dsh-draw 0.1.2 → 0.2.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.
- package/CHANGELOG.md +31 -0
- package/README.es.md +2 -1
- package/README.hi.md +2 -1
- package/README.md +4 -3
- package/README.pt.md +2 -1
- package/README.zh.md +2 -1
- package/cordis.patch.yml +5 -1
- package/lib/client.js +4 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +173 -9
- package/lib/types/client/present.d.ts +3 -1
- package/lib/types/client/present.d.ts.map +1 -1
- package/lib/types/client/present.js +4 -2
- package/lib/types/config.d.ts +6 -0
- package/lib/types/config.d.ts.map +1 -1
- package/lib/types/config.js +8 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +1 -0
- package/lib/types/provider.d.ts +39 -0
- package/lib/types/provider.d.ts.map +1 -0
- package/lib/types/provider.js +192 -0
- package/lib/types/router.d.ts +1 -3
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +3 -7
- package/lib/types/version.d.ts +4 -3
- package/lib/types/version.d.ts.map +1 -1
- package/lib/types/version.js +4 -3
- package/package.json +28 -2
- package/src/client/present.ts +4 -2
- package/src/config.ts +15 -0
- package/src/index.ts +1 -0
- package/src/provider.ts +237 -0
- package/src/router.ts +4 -8
- package/src/version.ts +4 -3
package/lib/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const DEFAULT_ENGINES = Object.freeze([Object.freeze({
|
|
|
39
39
|
baseUrl: "https://api.openai.com/v1",
|
|
40
40
|
model: "gpt-image-1",
|
|
41
41
|
apiKeyRef: "OPENAI_API_KEY",
|
|
42
|
+
provider: "openai",
|
|
42
43
|
enabled: true,
|
|
43
44
|
sizeMap: Object.freeze({
|
|
44
45
|
square: "1024x1024",
|
|
@@ -55,6 +56,7 @@ const DEFAULT_ENGINES = Object.freeze([Object.freeze({
|
|
|
55
56
|
baseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
56
57
|
model: "cogview-3-flash",
|
|
57
58
|
apiKeyRef: "ZHIPU_API_KEY",
|
|
59
|
+
provider: "openai",
|
|
58
60
|
enabled: true,
|
|
59
61
|
sizeMap: Object.freeze({
|
|
60
62
|
square: "1024x1024",
|
|
@@ -81,6 +83,11 @@ const Config = z.object({
|
|
|
81
83
|
baseUrl: z.string().min(1).max(2048),
|
|
82
84
|
model: z.string().min(1).max(128),
|
|
83
85
|
apiKeyRef: z.string().min(1).max(128),
|
|
86
|
+
provider: z.union([
|
|
87
|
+
"openai",
|
|
88
|
+
"replicate",
|
|
89
|
+
"fal"
|
|
90
|
+
]),
|
|
84
91
|
enabled: z.boolean(),
|
|
85
92
|
sizeMap: z.object({
|
|
86
93
|
square: z.string().min(1).max(64),
|
|
@@ -141,6 +148,8 @@ function resolveConfig(config) {
|
|
|
141
148
|
if (model === "") throw new Error(`dsh-draw: engine "${id}" model must be a non-empty string of at most 128 characters`);
|
|
142
149
|
const apiKeyRef = typeof engine.apiKeyRef === "string" && API_KEY_REF_PATTERN.test(engine.apiKeyRef) ? engine.apiKeyRef : "";
|
|
143
150
|
if (apiKeyRef === "") throw new Error(`dsh-draw: engine "${id}" apiKeyRef ${JSON.stringify(engine.apiKeyRef)} must be an environment-variable name`);
|
|
151
|
+
const provider = engine.provider ?? "openai";
|
|
152
|
+
if (provider !== "openai" && provider !== "replicate" && provider !== "fal") throw new Error(`dsh-draw: engine "${id}" provider must be one of "openai", "replicate", "fal", got ${JSON.stringify(provider)}`);
|
|
144
153
|
const sizeMap = engine.sizeMap === void 0 ? DEFAULT_SIZE_MAP : engine.sizeMap;
|
|
145
154
|
for (const key of [
|
|
146
155
|
"square",
|
|
@@ -166,6 +175,7 @@ function resolveConfig(config) {
|
|
|
166
175
|
baseUrl: baseUrl.replace(/\/+$/u, ""),
|
|
167
176
|
model,
|
|
168
177
|
apiKeyRef,
|
|
178
|
+
provider,
|
|
169
179
|
enabled,
|
|
170
180
|
sizeMap: Object.freeze({ ...sizeMap }),
|
|
171
181
|
qualitySupported,
|
|
@@ -948,6 +958,163 @@ async function downloadImageUrl(engine, url, credential, deps, signal) {
|
|
|
948
958
|
}
|
|
949
959
|
}
|
|
950
960
|
//#endregion
|
|
961
|
+
//#region src/provider.ts
|
|
962
|
+
/** The OpenAI-compatible adapter: delegates to the existing `/images/generations` path. */
|
|
963
|
+
const openaiProvider = {
|
|
964
|
+
vocabulary: "openai",
|
|
965
|
+
generate(engine, request, deps, signal) {
|
|
966
|
+
return callEngine(engine, translateRequest(engine, request), deps, signal);
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
/** Replicate prediction polling interval (ms). */
|
|
970
|
+
const REPLICATE_POLL_INTERVAL_MS = 1e3;
|
|
971
|
+
/** Maximum prediction polls before the call fails as a slow request. */
|
|
972
|
+
const REPLICATE_MAX_POLLS = 120;
|
|
973
|
+
/** Parse one concrete `WIDTHxHEIGHT` size string into dimensions. */
|
|
974
|
+
function dimensionsOf(size) {
|
|
975
|
+
const match = /^(\d{2,5})x(\d{2,5})$/u.exec(size);
|
|
976
|
+
if (match === null) return void 0;
|
|
977
|
+
return {
|
|
978
|
+
width: Number(match[1]),
|
|
979
|
+
height: Number(match[2])
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
/** Resolve an engine credential, failing closed when it is unconfigured. */
|
|
983
|
+
async function requireCredential(engine, deps) {
|
|
984
|
+
const credential = await deps.resolveCredential(engine.apiKeyRef);
|
|
985
|
+
if (credential === void 0) throw new EngineCallError("credential", "unconfigured", `engine "${engine.id}" has no resolved credential reference ${engine.apiKeyRef}`);
|
|
986
|
+
return credential;
|
|
987
|
+
}
|
|
988
|
+
/** Parse a JSON response body, failing with a structured parse error. */
|
|
989
|
+
function parseJson(engine, body) {
|
|
990
|
+
let parsed;
|
|
991
|
+
try {
|
|
992
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
993
|
+
} catch (cause) {
|
|
994
|
+
throw new EngineCallError("parse", "parse", `engine "${engine.id}" returned a non-JSON response`, { cause });
|
|
995
|
+
}
|
|
996
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new EngineCallError("parse", "parse", `engine "${engine.id}" returned a non-object payload`);
|
|
997
|
+
return parsed;
|
|
998
|
+
}
|
|
999
|
+
/** Download one image URL with an explicit authorization header value. */
|
|
1000
|
+
async function downloadImage(engine, url, authorization, deps, signal) {
|
|
1001
|
+
const response = await deps.transport.request({
|
|
1002
|
+
method: "GET",
|
|
1003
|
+
url,
|
|
1004
|
+
headers: { authorization },
|
|
1005
|
+
...signal === void 0 ? {} : { signal }
|
|
1006
|
+
});
|
|
1007
|
+
if (response.status < 200 || response.status >= 300) throw new EngineCallError("request", "http", `engine "${engine.id}" image download failed with HTTP ${response.status}`, { status: response.status });
|
|
1008
|
+
return response.body;
|
|
1009
|
+
}
|
|
1010
|
+
/** Sleep without swallowing the caller's abort (checked by the caller's loop). */
|
|
1011
|
+
function sleep(ms) {
|
|
1012
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1013
|
+
}
|
|
1014
|
+
/** The provider registry, keyed by vocabulary. */
|
|
1015
|
+
const PROVIDERS = Object.freeze({
|
|
1016
|
+
openai: openaiProvider,
|
|
1017
|
+
replicate: {
|
|
1018
|
+
vocabulary: "replicate",
|
|
1019
|
+
async generate(engine, request, deps, signal) {
|
|
1020
|
+
const credential = await requireCredential(engine, deps);
|
|
1021
|
+
const dimensions = dimensionsOf(engine.sizeMap[request.size ?? "square"]);
|
|
1022
|
+
const input = {
|
|
1023
|
+
prompt: request.prompt,
|
|
1024
|
+
num_outputs: request.count ?? 1,
|
|
1025
|
+
...dimensions === void 0 ? {} : {
|
|
1026
|
+
width: dimensions.width,
|
|
1027
|
+
height: dimensions.height
|
|
1028
|
+
}
|
|
1029
|
+
};
|
|
1030
|
+
const create = await deps.transport.request({
|
|
1031
|
+
method: "POST",
|
|
1032
|
+
url: `${engine.baseUrl}/models/${engine.model}/predictions`,
|
|
1033
|
+
headers: {
|
|
1034
|
+
"content-type": "application/json",
|
|
1035
|
+
authorization: `Bearer ${credential}`
|
|
1036
|
+
},
|
|
1037
|
+
body: new TextEncoder().encode(JSON.stringify({ input })),
|
|
1038
|
+
...signal === void 0 ? {} : { signal }
|
|
1039
|
+
});
|
|
1040
|
+
if (create.status === 401 || create.status === 403) throw new EngineCallError("request", "auth", `engine "${engine.id}" rejected the credential (HTTP ${create.status})`, { status: create.status });
|
|
1041
|
+
if (create.status < 200 || create.status >= 300) throw new EngineCallError("request", "http", `engine "${engine.id}" failed with HTTP ${create.status}`, { status: create.status });
|
|
1042
|
+
let prediction = parseJson(engine, create.body);
|
|
1043
|
+
const id = typeof prediction["id"] === "string" ? prediction["id"] : "";
|
|
1044
|
+
const urls = typeof prediction["urls"] === "object" && prediction["urls"] !== null ? prediction["urls"] : {};
|
|
1045
|
+
const pollUrl = typeof urls["get"] === "string" && urls["get"] !== "" ? urls["get"] : `${engine.baseUrl}/predictions/${id}`;
|
|
1046
|
+
for (let poll = 0; poll < REPLICATE_MAX_POLLS; poll += 1) {
|
|
1047
|
+
signal?.throwIfAborted();
|
|
1048
|
+
const status = typeof prediction["status"] === "string" ? prediction["status"] : "";
|
|
1049
|
+
if (status === "succeeded") break;
|
|
1050
|
+
if (status === "failed" || status === "canceled") throw new EngineCallError("request", "http", `engine "${engine.id}" prediction ${status}`);
|
|
1051
|
+
await sleep(REPLICATE_POLL_INTERVAL_MS);
|
|
1052
|
+
const polled = await deps.transport.request({
|
|
1053
|
+
method: "GET",
|
|
1054
|
+
url: pollUrl,
|
|
1055
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
1056
|
+
...signal === void 0 ? {} : { signal }
|
|
1057
|
+
});
|
|
1058
|
+
if (polled.status < 200 || polled.status >= 300) throw new EngineCallError("request", "http", `engine "${engine.id}" prediction poll failed with HTTP ${polled.status}`, { status: polled.status });
|
|
1059
|
+
prediction = parseJson(engine, polled.body);
|
|
1060
|
+
}
|
|
1061
|
+
if (prediction["status"] !== "succeeded") throw new EngineCallError("request", "http", `engine "${engine.id}" prediction did not finish within ${REPLICATE_MAX_POLLS} polls`);
|
|
1062
|
+
const output = prediction["output"];
|
|
1063
|
+
if (!Array.isArray(output)) throw new EngineCallError("parse", "parse", `engine "${engine.id}" prediction has no output array`);
|
|
1064
|
+
const images = [];
|
|
1065
|
+
for (const item of output) {
|
|
1066
|
+
if (typeof item !== "string" || item === "") throw new EngineCallError("parse", "parse", `engine "${engine.id}" prediction returned a non-URL output entry`);
|
|
1067
|
+
images.push({
|
|
1068
|
+
data: await downloadImage(engine, item, `Bearer ${credential}`, deps, signal),
|
|
1069
|
+
mediaType: engine.imageMediaType
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
if (images.length === 0) throw new EngineCallError("parse", "parse", `engine "${engine.id}" prediction returned no images`);
|
|
1073
|
+
return images;
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
fal: {
|
|
1077
|
+
vocabulary: "fal",
|
|
1078
|
+
async generate(engine, request, deps, signal) {
|
|
1079
|
+
const credential = await requireCredential(engine, deps);
|
|
1080
|
+
const body = {
|
|
1081
|
+
prompt: request.prompt,
|
|
1082
|
+
image_size: engine.sizeMap[request.size ?? "square"],
|
|
1083
|
+
num_images: request.count ?? 1
|
|
1084
|
+
};
|
|
1085
|
+
const response = await deps.transport.request({
|
|
1086
|
+
method: "POST",
|
|
1087
|
+
url: `${engine.baseUrl}/${engine.model}`,
|
|
1088
|
+
headers: {
|
|
1089
|
+
"content-type": "application/json",
|
|
1090
|
+
authorization: `Key ${credential}`
|
|
1091
|
+
},
|
|
1092
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
1093
|
+
...signal === void 0 ? {} : { signal }
|
|
1094
|
+
});
|
|
1095
|
+
if (response.status === 401 || response.status === 403) throw new EngineCallError("request", "auth", `engine "${engine.id}" rejected the credential (HTTP ${response.status})`, { status: response.status });
|
|
1096
|
+
if (response.status < 200 || response.status >= 300) throw new EngineCallError("request", "http", `engine "${engine.id}" failed with HTTP ${response.status}`, { status: response.status });
|
|
1097
|
+
const entries = parseJson(engine, response.body)["images"];
|
|
1098
|
+
if (!Array.isArray(entries)) throw new EngineCallError("parse", "parse", `engine "${engine.id}" response has no images array`);
|
|
1099
|
+
const images = [];
|
|
1100
|
+
for (const raw of entries) {
|
|
1101
|
+
const url = typeof raw === "object" && raw !== null ? raw["url"] : void 0;
|
|
1102
|
+
if (typeof url !== "string" || url === "") throw new EngineCallError("parse", "parse", `engine "${engine.id}" returned an image entry without a URL`);
|
|
1103
|
+
images.push({
|
|
1104
|
+
data: await downloadImage(engine, url, `Key ${credential}`, deps, signal),
|
|
1105
|
+
mediaType: engine.imageMediaType
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
if (images.length === 0) throw new EngineCallError("parse", "parse", `engine "${engine.id}" returned no images`);
|
|
1109
|
+
return images;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
/** Resolve the provider for one vocabulary (always present — the config gate restricts the value). */
|
|
1114
|
+
function providerFor(vocabulary) {
|
|
1115
|
+
return PROVIDERS[vocabulary];
|
|
1116
|
+
}
|
|
1117
|
+
//#endregion
|
|
951
1118
|
//#region src/router.ts
|
|
952
1119
|
/**
|
|
953
1120
|
* The engine chain with per-engine health and cooldown. All mutations are
|
|
@@ -1001,7 +1168,7 @@ var EngineRouter = class {
|
|
|
1001
1168
|
}
|
|
1002
1169
|
tried += 1;
|
|
1003
1170
|
try {
|
|
1004
|
-
const images = await
|
|
1171
|
+
const images = await providerFor(engine.provider).generate(engine, request, deps, signal);
|
|
1005
1172
|
this.recordSuccess(engine.id);
|
|
1006
1173
|
attempts.push({
|
|
1007
1174
|
engine: engine.id,
|
|
@@ -1145,10 +1312,6 @@ var EngineRouter = class {
|
|
|
1145
1312
|
message: `engine "${engine.id}" is cooling down after repeated failures`
|
|
1146
1313
|
};
|
|
1147
1314
|
}
|
|
1148
|
-
/** Translate the standard request against one engine (the pure translate step). */
|
|
1149
|
-
translate(engine, request) {
|
|
1150
|
-
return translateRequest(engine, request);
|
|
1151
|
-
}
|
|
1152
1315
|
/** Record a success: reset consecutive failures and cooldown. */
|
|
1153
1316
|
recordSuccess(engineId) {
|
|
1154
1317
|
const health = this.health.get(engineId);
|
|
@@ -1185,13 +1348,14 @@ var EngineRouter = class {
|
|
|
1185
1348
|
//#region src/version.ts
|
|
1186
1349
|
/**
|
|
1187
1350
|
* The plugin version, hardcoded beside `package.json` so the release script
|
|
1188
|
-
* bumps both and `tests/
|
|
1189
|
-
* `draw/status` snapshot so the settings panel
|
|
1351
|
+
* bumps both and the version tripwire in `tests/session-events.spec.ts` trips
|
|
1352
|
+
* when they drift. Served in the `draw/status` snapshot so the settings panel
|
|
1353
|
+
* shows which plugin build runs.
|
|
1190
1354
|
*
|
|
1191
1355
|
* @module dsh-draw/version
|
|
1192
1356
|
*/
|
|
1193
1357
|
/** Plugin version; must equal the `version` field in `package.json`. */
|
|
1194
|
-
const PLUGIN_VERSION = "0.1
|
|
1358
|
+
const PLUGIN_VERSION = "0.2.1";
|
|
1195
1359
|
//#endregion
|
|
1196
1360
|
//#region src/service.ts
|
|
1197
1361
|
/**
|
|
@@ -1554,4 +1718,4 @@ async function apply(ctx, config) {
|
|
|
1554
1718
|
logger.info(`image generation enabled: ${resolved.engines.map((engine) => engine.id).join(", ")} (preferred ${resolved.defaultEngine})`);
|
|
1555
1719
|
}
|
|
1556
1720
|
//#endregion
|
|
1557
|
-
export { Config, DEFAULT_ENGINES, DRAW_INVOCATIONS, DrawService, Drawer, EngineCallError, EngineRouter, HttpError, PLUGIN_VERSION, REDACTED, appendDrawGenerated, apply, callEngine, checkQuotaBytes, checkQuotaGenerations, commitDrawGenerated, defaultHttpTransport, drawGeneratedEvents, engineById, fallbackDrawGeneratedEvents, fusedSignal, imageGenerateTool, imageToWire, inject, makeEventGate, makeHostEventGate, name, normalizeRequest, probeIgnorableAppend, probeToWire, quotaState, resolveConfig, sanitizeError, sanitizeText, sanitizeUrl, statusToView, translateRequest };
|
|
1721
|
+
export { Config, DEFAULT_ENGINES, DRAW_INVOCATIONS, DrawService, Drawer, EngineCallError, EngineRouter, HttpError, PLUGIN_VERSION, PROVIDERS, REDACTED, appendDrawGenerated, apply, callEngine, checkQuotaBytes, checkQuotaGenerations, commitDrawGenerated, defaultHttpTransport, drawGeneratedEvents, engineById, fallbackDrawGeneratedEvents, fusedSignal, imageGenerateTool, imageToWire, inject, makeEventGate, makeHostEventGate, name, normalizeRequest, probeIgnorableAppend, probeToWire, providerFor, quotaState, resolveConfig, sanitizeError, sanitizeText, sanitizeUrl, statusToView, translateRequest };
|
|
@@ -74,7 +74,9 @@ export interface PresentedDrawPanel {
|
|
|
74
74
|
* Project the panel snapshot onto the settings row model.
|
|
75
75
|
*
|
|
76
76
|
* @param snapshot - the `draw/status` wire snapshot.
|
|
77
|
+
* @param now - clock override for the cooldown flag (default {@link Date.now});
|
|
78
|
+
* injected so the presenter stays a pure function of its inputs.
|
|
77
79
|
* @returns the presented panel model.
|
|
78
80
|
*/
|
|
79
|
-
export declare function presentDrawPanel(snapshot: DrawStatusSnapshot): PresentedDrawPanel;
|
|
81
|
+
export declare function presentDrawPanel(snapshot: DrawStatusSnapshot, now?: () => number): PresentedDrawPanel;
|
|
80
82
|
//# sourceMappingURL=present.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../../src/client/present.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAA;IACrB,mCAAmC;IACnC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAA;IACjC,8CAA8C;IAC9C,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,wBAAwB;IACxB,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,mBAAmB,GAAG,SAAS,CAuCvF;AAED,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACtC,KAAK,EAAE;QAAE,wBAAwB,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAA;IACvE,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../../src/client/present.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAA;AAC3E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,8CAA8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,uCAAuC;AACvC,MAAM,WAAW,mBAAmB;IAClC,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAA;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAA;IACrB,mCAAmC;IACnC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAA;IACjC,8CAA8C;IAC9C,KAAK,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,wBAAwB;IACxB,MAAM,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,mBAAmB,GAAG,SAAS,CAuCvF;AAED,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,mBAAmB,EAAE,MAAM,CAAA;IAC3B,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACtC,KAAK,EAAE;QAAE,wBAAwB,EAAE,MAAM,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAA;IACvE,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,kBAAkB,CAqB/G"}
|
|
@@ -60,9 +60,11 @@ export function presentDrawResult(block) {
|
|
|
60
60
|
* Project the panel snapshot onto the settings row model.
|
|
61
61
|
*
|
|
62
62
|
* @param snapshot - the `draw/status` wire snapshot.
|
|
63
|
+
* @param now - clock override for the cooldown flag (default {@link Date.now});
|
|
64
|
+
* injected so the presenter stays a pure function of its inputs.
|
|
63
65
|
* @returns the presented panel model.
|
|
64
66
|
*/
|
|
65
|
-
export function presentDrawPanel(snapshot) {
|
|
67
|
+
export function presentDrawPanel(snapshot, now = Date.now) {
|
|
66
68
|
return {
|
|
67
69
|
pluginVersion: snapshot.pluginVersion,
|
|
68
70
|
engines: snapshot.engines.map(engine => ({
|
|
@@ -76,7 +78,7 @@ export function presentDrawPanel(snapshot) {
|
|
|
76
78
|
...(engine.credential.source !== undefined ? { credentialSource: engine.credential.source } : {}),
|
|
77
79
|
credentialWritable: engine.credential.writable,
|
|
78
80
|
consecutiveFailures: engine.health.consecutiveFailures,
|
|
79
|
-
coolingDown: engine.health.cooldownUntil !== null && engine.health.cooldownUntil >
|
|
81
|
+
coolingDown: engine.health.cooldownUntil !== null && engine.health.cooldownUntil > now(),
|
|
80
82
|
lastError: engine.health.lastError,
|
|
81
83
|
})),
|
|
82
84
|
quota: { maxGenerationsPerSession: snapshot.quota.maxGenerationsPerSession, maxBytesPerSession: snapshot.quota.maxBytesPerSession },
|
package/lib/types/config.d.ts
CHANGED
|
@@ -51,6 +51,8 @@ export interface EngineSizeMap {
|
|
|
51
51
|
}
|
|
52
52
|
/** Whether generated images arrive as base64 payloads or downloadable URLs. */
|
|
53
53
|
export type EngineResponseFormat = 'b64_json' | 'url';
|
|
54
|
+
/** The image-generation vocabularies a configured engine may declare (provider seam). */
|
|
55
|
+
export type EngineVocabulary = 'openai' | 'replicate' | 'fal';
|
|
54
56
|
/** One OpenAI-compatible image-generation endpoint the router may use. */
|
|
55
57
|
export interface EngineConfig {
|
|
56
58
|
/** Stable engine id used in tool args, results, and the settings panel. */
|
|
@@ -61,6 +63,8 @@ export interface EngineConfig {
|
|
|
61
63
|
model: string;
|
|
62
64
|
/** Credential reference (environment-variable name) the API key resolves from; never a literal key. */
|
|
63
65
|
apiKeyRef: string;
|
|
66
|
+
/** Which request/response vocabulary this engine speaks (default `openai`). */
|
|
67
|
+
provider?: EngineVocabulary;
|
|
64
68
|
/** Whether the router may use this engine (default true). */
|
|
65
69
|
enabled?: boolean;
|
|
66
70
|
/** Standard-size → concrete-size translation (default: the OpenAI sizes). */
|
|
@@ -109,6 +113,8 @@ export interface ResolvedEngineConfig {
|
|
|
109
113
|
model: string;
|
|
110
114
|
/** Credential reference the API key resolves from. */
|
|
111
115
|
apiKeyRef: string;
|
|
116
|
+
/** Request/response vocabulary this engine speaks. */
|
|
117
|
+
provider: EngineVocabulary;
|
|
112
118
|
/** Whether the router may use this engine. */
|
|
113
119
|
enabled: boolean;
|
|
114
120
|
/** Standard-size → concrete-size translation. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEjE,2DAA2D;AAC3D,eAAO,MAAM,0BAA0B,SAAU,CAAA;AAEjD,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,SAAU,CAAA;AAE7C,uDAAuD;AACvD,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,KAAK,CAAA;AAErC,8CAA8C;AAC9C,eAAO,MAAM,yBAAyB,OAAQ,CAAA;AAE9C,wCAAwC;AACxC,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC,+CAA+C;AAC/C,eAAO,MAAM,mCAAmC,MAAM,CAAA;AAEtD,sDAAsD;AACtD,eAAO,MAAM,2BAA2B,SAAU,CAAA;AAElD,oDAAoD;AACpD,eAAO,MAAM,6BAA6B,QAAoB,CAAA;AAE9D,yDAAyD;AACzD,eAAO,MAAM,qBAAqB,QAAyB,CAAA;AAE3D,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,IAAI,CAAA;AAE1C,oDAAoD;AACpD,eAAO,MAAM,qBAAqB,KAAK,CAAA;AAEvC,sEAAsE;AACtE,eAAO,MAAM,mBAAmB,QAAS,CAAA;AAEzC,sCAAsC;AACtC,eAAO,MAAM,eAAe,UAAY,CAAA;AAExC,uFAAuF;AACvF,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,CAAA;AAEvE,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;CACb;AAED,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,KAAK,CAAA;AAErD,yFAAyF;AACzF,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAA;AAE7D,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,EAAE,EAAE,MAAM,CAAA;IACV,gGAAgG;IAChG,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb,uGAAuG;IACvG,SAAS,EAAE,MAAM,CAAA;IACjB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,gBAAgB,CAAA;IAC3B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,aAAa,CAAA;IACvB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,oGAAoG;IACpG,cAAc,CAAC,EAAE,oBAAoB,CAAA;IACrC,8DAA8D;IAC9D,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC;AAED,4BAA4B;AAC5B,MAAM,WAAW,MAAM;IACrB,oFAAoF;IACpF,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB,4GAA4G;IAC5G,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,qDAAqD;IACrD,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,gEAAgE;IAChE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,uEAAuE;AACvE,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,QAAQ,CAAC,YAAY,CAAC,CA2BhE,CAAA;AAEF,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,aAAa,CAKnD,CAAA;AAEF,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAA;IAChB,iDAAiD;IACjD,OAAO,EAAE,aAAa,CAAA;IACtB,8DAA8D;IAC9D,gBAAgB,EAAE,OAAO,CAAA;IACzB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAA;IACvB,kCAAkC;IAClC,cAAc,EAAE,oBAAoB,CAAA;IACpC,wCAAwC;IACxC,cAAc,EAAE,cAAc,CAAA;CAC/B;AAED,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACxC,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAA;IACrB,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAA;IACxB,0CAA0C;IAC1C,gBAAgB,EAAE,MAAM,CAAA;IACxB,uCAAuC;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB,uCAAuC;IACvC,wBAAwB,EAAE,MAAM,CAAA;IAChC,4CAA4C;IAC5C,kBAAkB,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,CAAA;IACxB,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,6DAA6D;AAC7D,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CA2B3B,CAAA;AAOF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,cAAc,CAkHxE;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAE/F"}
|
package/lib/types/config.js
CHANGED
|
@@ -42,6 +42,7 @@ export const DEFAULT_ENGINES = Object.freeze([
|
|
|
42
42
|
baseUrl: 'https://api.openai.com/v1',
|
|
43
43
|
model: 'gpt-image-1',
|
|
44
44
|
apiKeyRef: 'OPENAI_API_KEY',
|
|
45
|
+
provider: 'openai',
|
|
45
46
|
enabled: true,
|
|
46
47
|
sizeMap: Object.freeze({ square: '1024x1024', landscape: '1536x1024', portrait: '1024x1536', auto: 'auto' }),
|
|
47
48
|
qualitySupported: true,
|
|
@@ -54,6 +55,7 @@ export const DEFAULT_ENGINES = Object.freeze([
|
|
|
54
55
|
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
55
56
|
model: 'cogview-3-flash',
|
|
56
57
|
apiKeyRef: 'ZHIPU_API_KEY',
|
|
58
|
+
provider: 'openai',
|
|
57
59
|
enabled: true,
|
|
58
60
|
sizeMap: Object.freeze({ square: '1024x1024', landscape: '1344x768', portrait: '768x1344', auto: '1024x1024' }),
|
|
59
61
|
qualitySupported: false,
|
|
@@ -76,6 +78,7 @@ export const Config = z.object({
|
|
|
76
78
|
baseUrl: z.string().min(1).max(2048),
|
|
77
79
|
model: z.string().min(1).max(128),
|
|
78
80
|
apiKeyRef: z.string().min(1).max(128),
|
|
81
|
+
provider: z.union(['openai', 'replicate', 'fal']),
|
|
79
82
|
enabled: z.boolean(),
|
|
80
83
|
sizeMap: z.object({
|
|
81
84
|
square: z.string().min(1).max(64),
|
|
@@ -141,6 +144,10 @@ export function resolveConfig(config) {
|
|
|
141
144
|
const apiKeyRef = typeof engine.apiKeyRef === 'string' && API_KEY_REF_PATTERN.test(engine.apiKeyRef) ? engine.apiKeyRef : '';
|
|
142
145
|
if (apiKeyRef === '')
|
|
143
146
|
throw new Error(`dsh-draw: engine "${id}" apiKeyRef ${JSON.stringify(engine.apiKeyRef)} must be an environment-variable name`);
|
|
147
|
+
const provider = engine.provider ?? 'openai';
|
|
148
|
+
if (provider !== 'openai' && provider !== 'replicate' && provider !== 'fal') {
|
|
149
|
+
throw new Error(`dsh-draw: engine "${id}" provider must be one of "openai", "replicate", "fal", got ${JSON.stringify(provider)}`);
|
|
150
|
+
}
|
|
144
151
|
const sizeMap = engine.sizeMap === undefined ? DEFAULT_SIZE_MAP : engine.sizeMap;
|
|
145
152
|
for (const key of ['square', 'landscape', 'portrait', 'auto']) {
|
|
146
153
|
const value = sizeMap[key];
|
|
@@ -170,6 +177,7 @@ export function resolveConfig(config) {
|
|
|
170
177
|
baseUrl: baseUrl.replace(/\/+$/u, ''),
|
|
171
178
|
model,
|
|
172
179
|
apiKeyRef,
|
|
180
|
+
provider,
|
|
173
181
|
enabled,
|
|
174
182
|
sizeMap: Object.freeze({ ...sizeMap }),
|
|
175
183
|
qualitySupported,
|
package/lib/types/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { imageGenerateTool } from './tool.js';
|
|
|
25
25
|
export { DrawService } from './service.js';
|
|
26
26
|
export { defaultHttpTransport, fusedSignal, type HttpTransport, type HttpRequest, type HttpResponse, HttpError } from './http.js';
|
|
27
27
|
export { callEngine, EngineCallError, type EngineDeps, type ProducedImage } from './engine.js';
|
|
28
|
+
export { providerFor, PROVIDERS, type EngineProvider, type EngineVocabulary } from './provider.js';
|
|
28
29
|
export { translateRequest, normalizeRequest, type StandardImageRequest } from './translate.js';
|
|
29
30
|
export { quotaState, checkQuotaGenerations, checkQuotaBytes, type QuotaLimits, type QuotaState } from './quota.js';
|
|
30
31
|
export { sanitizeUrl, sanitizeText, sanitizeError, REDACTED } from './sanitize.js';
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAA;AAQnD,eAAO,MAAM,IAAI,aAAa,CAAA;AAE9B,oEAAoE;AACpE,eAAO,MAAM,MAAM,UAAY,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAChI,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAClG,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAClI,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACjI,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAClH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACzJ,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAChI,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAK7G;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvE"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAMlD,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAA;AAQnD,eAAO,MAAM,IAAI,aAAa,CAAA;AAE9B,oEAAoE;AACpE,eAAO,MAAM,MAAM,UAAY,CAAA;AAE/B,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,MAAM,IAAI,UAAU,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAChI,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAClG,OAAO,EAAE,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAA;AAClI,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACjI,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;AAC9F,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAClG,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAC9F,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAA;AAClH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAClF,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACzJ,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,KAAK,SAAS,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAChI,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAK7G;;;;;;;GAOG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCvE"}
|
package/lib/types/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { imageGenerateTool } from './tool.js';
|
|
|
31
31
|
export { DrawService } from './service.js';
|
|
32
32
|
export { defaultHttpTransport, fusedSignal, HttpError } from './http.js';
|
|
33
33
|
export { callEngine, EngineCallError } from './engine.js';
|
|
34
|
+
export { providerFor, PROVIDERS } from './provider.js';
|
|
34
35
|
export { translateRequest, normalizeRequest } from './translate.js';
|
|
35
36
|
export { quotaState, checkQuotaGenerations, checkQuotaBytes } from './quota.js';
|
|
36
37
|
export { sanitizeUrl, sanitizeText, sanitizeError, REDACTED } from './sanitize.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine-provider seam: one adapter per image-generation vocabulary. The
|
|
3
|
+
* OpenAI provider is the existing OpenAI-compatible `POST /images/generations`
|
|
4
|
+
* adapter; `replicate` and `fal` adapters speak those vendors' native request
|
|
5
|
+
* and response shapes (prediction polling and `fal.run` queue respectively).
|
|
6
|
+
* A configured engine declares its vocabulary (`provider`); the router
|
|
7
|
+
* dispatches through {@link providerFor}, so cooldown fallback, quota, and the
|
|
8
|
+
* audit path stay vocabulary-agnostic.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-draw/provider
|
|
11
|
+
*/
|
|
12
|
+
import type { ResolvedEngineConfig } from './config.js';
|
|
13
|
+
import { type EngineDeps, type ProducedImage } from './engine.js';
|
|
14
|
+
import type { StandardImageRequest } from './translate.js';
|
|
15
|
+
/** The image-generation vocabularies a configured engine may declare. */
|
|
16
|
+
export type EngineVocabulary = 'openai' | 'replicate' | 'fal';
|
|
17
|
+
/**
|
|
18
|
+
* One engine adapter: translates the standard tool request into a vendor
|
|
19
|
+
* request, calls it, and returns produced image bytes. A provider must never
|
|
20
|
+
* leak the credential — `deps.resolveCredential` resolves it per call.
|
|
21
|
+
*/
|
|
22
|
+
export interface EngineProvider {
|
|
23
|
+
/** Which vocabulary this adapter speaks. */
|
|
24
|
+
readonly vocabulary: EngineVocabulary;
|
|
25
|
+
/**
|
|
26
|
+
* Generate images for one standardized request.
|
|
27
|
+
* @param engine - resolved engine configuration.
|
|
28
|
+
* @param request - normalized standard request.
|
|
29
|
+
* @param deps - transport and credential resolution.
|
|
30
|
+
* @param signal - caller cancellation.
|
|
31
|
+
* @returns the produced images.
|
|
32
|
+
*/
|
|
33
|
+
generate(engine: ResolvedEngineConfig, request: StandardImageRequest, deps: EngineDeps, signal?: AbortSignal): Promise<ProducedImage[]>;
|
|
34
|
+
}
|
|
35
|
+
/** The provider registry, keyed by vocabulary. */
|
|
36
|
+
export declare const PROVIDERS: Readonly<Record<EngineVocabulary, EngineProvider>>;
|
|
37
|
+
/** Resolve the provider for one vocabulary (always present — the config gate restricts the value). */
|
|
38
|
+
export declare function providerFor(vocabulary: EngineVocabulary): EngineProvider;
|
|
39
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAG1D,yEAAyE;AACzE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,WAAW,GAAG,KAAK,CAAA;AAE7D;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAA;IACrC;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;CACxI;AAwLD,kDAAkD;AAClD,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAIvE,CAAA;AAEF,sGAAsG;AACtG,wBAAgB,WAAW,CAAC,UAAU,EAAE,gBAAgB,GAAG,cAAc,CAExE"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine-provider seam: one adapter per image-generation vocabulary. The
|
|
3
|
+
* OpenAI provider is the existing OpenAI-compatible `POST /images/generations`
|
|
4
|
+
* adapter; `replicate` and `fal` adapters speak those vendors' native request
|
|
5
|
+
* and response shapes (prediction polling and `fal.run` queue respectively).
|
|
6
|
+
* A configured engine declares its vocabulary (`provider`); the router
|
|
7
|
+
* dispatches through {@link providerFor}, so cooldown fallback, quota, and the
|
|
8
|
+
* audit path stay vocabulary-agnostic.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-draw/provider
|
|
11
|
+
*/
|
|
12
|
+
import { callEngine, EngineCallError, } from './engine.js';
|
|
13
|
+
import { translateRequest } from './translate.js';
|
|
14
|
+
/** The OpenAI-compatible adapter: delegates to the existing `/images/generations` path. */
|
|
15
|
+
const openaiProvider = {
|
|
16
|
+
vocabulary: 'openai',
|
|
17
|
+
generate(engine, request, deps, signal) {
|
|
18
|
+
return callEngine(engine, translateRequest(engine, request), deps, signal);
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/** Replicate prediction polling interval (ms). */
|
|
22
|
+
const REPLICATE_POLL_INTERVAL_MS = 1_000;
|
|
23
|
+
/** Maximum prediction polls before the call fails as a slow request. */
|
|
24
|
+
const REPLICATE_MAX_POLLS = 120;
|
|
25
|
+
/** Parse one concrete `WIDTHxHEIGHT` size string into dimensions. */
|
|
26
|
+
function dimensionsOf(size) {
|
|
27
|
+
const match = /^(\d{2,5})x(\d{2,5})$/u.exec(size);
|
|
28
|
+
if (match === null)
|
|
29
|
+
return undefined;
|
|
30
|
+
return { width: Number(match[1]), height: Number(match[2]) };
|
|
31
|
+
}
|
|
32
|
+
/** Resolve an engine credential, failing closed when it is unconfigured. */
|
|
33
|
+
async function requireCredential(engine, deps) {
|
|
34
|
+
const credential = await deps.resolveCredential(engine.apiKeyRef);
|
|
35
|
+
if (credential === undefined) {
|
|
36
|
+
throw new EngineCallError('credential', 'unconfigured', `engine "${engine.id}" has no resolved credential reference ${engine.apiKeyRef}`);
|
|
37
|
+
}
|
|
38
|
+
return credential;
|
|
39
|
+
}
|
|
40
|
+
/** Parse a JSON response body, failing with a structured parse error. */
|
|
41
|
+
function parseJson(engine, body) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(new TextDecoder().decode(body));
|
|
45
|
+
}
|
|
46
|
+
catch (cause) {
|
|
47
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a non-JSON response`, { cause });
|
|
48
|
+
}
|
|
49
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
50
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned a non-object payload`);
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
/** Download one image URL with an explicit authorization header value. */
|
|
55
|
+
async function downloadImage(engine, url, authorization, deps, signal) {
|
|
56
|
+
const response = await deps.transport.request({
|
|
57
|
+
method: 'GET',
|
|
58
|
+
url,
|
|
59
|
+
headers: { authorization },
|
|
60
|
+
...(signal === undefined ? {} : { signal }),
|
|
61
|
+
});
|
|
62
|
+
if (response.status < 200 || response.status >= 300) {
|
|
63
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" image download failed with HTTP ${response.status}`, { status: response.status });
|
|
64
|
+
}
|
|
65
|
+
return response.body;
|
|
66
|
+
}
|
|
67
|
+
/** Sleep without swallowing the caller's abort (checked by the caller's loop). */
|
|
68
|
+
function sleep(ms) {
|
|
69
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
/** The Replicate adapter: create a prediction, poll to completion, then download its output URLs. */
|
|
72
|
+
const replicateProvider = {
|
|
73
|
+
vocabulary: 'replicate',
|
|
74
|
+
async generate(engine, request, deps, signal) {
|
|
75
|
+
const credential = await requireCredential(engine, deps);
|
|
76
|
+
const dimensions = dimensionsOf(engine.sizeMap[request.size ?? 'square']);
|
|
77
|
+
const input = {
|
|
78
|
+
prompt: request.prompt,
|
|
79
|
+
num_outputs: request.count ?? 1,
|
|
80
|
+
...(dimensions === undefined ? {} : { width: dimensions.width, height: dimensions.height }),
|
|
81
|
+
};
|
|
82
|
+
const create = await deps.transport.request({
|
|
83
|
+
method: 'POST',
|
|
84
|
+
url: `${engine.baseUrl}/models/${engine.model}/predictions`,
|
|
85
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${credential}` },
|
|
86
|
+
body: new TextEncoder().encode(JSON.stringify({ input })),
|
|
87
|
+
...(signal === undefined ? {} : { signal }),
|
|
88
|
+
});
|
|
89
|
+
if (create.status === 401 || create.status === 403) {
|
|
90
|
+
throw new EngineCallError('request', 'auth', `engine "${engine.id}" rejected the credential (HTTP ${create.status})`, { status: create.status });
|
|
91
|
+
}
|
|
92
|
+
if (create.status < 200 || create.status >= 300) {
|
|
93
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" failed with HTTP ${create.status}`, { status: create.status });
|
|
94
|
+
}
|
|
95
|
+
let prediction = parseJson(engine, create.body);
|
|
96
|
+
const id = typeof prediction['id'] === 'string' ? prediction['id'] : '';
|
|
97
|
+
const urls = typeof prediction['urls'] === 'object' && prediction['urls'] !== null
|
|
98
|
+
? prediction['urls']
|
|
99
|
+
: {};
|
|
100
|
+
const pollUrl = typeof urls['get'] === 'string' && urls['get'] !== '' ? urls['get'] : `${engine.baseUrl}/predictions/${id}`;
|
|
101
|
+
for (let poll = 0; poll < REPLICATE_MAX_POLLS; poll += 1) {
|
|
102
|
+
signal?.throwIfAborted();
|
|
103
|
+
const status = typeof prediction['status'] === 'string' ? prediction['status'] : '';
|
|
104
|
+
if (status === 'succeeded')
|
|
105
|
+
break;
|
|
106
|
+
if (status === 'failed' || status === 'canceled') {
|
|
107
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction ${status}`);
|
|
108
|
+
}
|
|
109
|
+
await sleep(REPLICATE_POLL_INTERVAL_MS);
|
|
110
|
+
const polled = await deps.transport.request({
|
|
111
|
+
method: 'GET',
|
|
112
|
+
url: pollUrl,
|
|
113
|
+
headers: { authorization: `Bearer ${credential}` },
|
|
114
|
+
...(signal === undefined ? {} : { signal }),
|
|
115
|
+
});
|
|
116
|
+
if (polled.status < 200 || polled.status >= 300) {
|
|
117
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction poll failed with HTTP ${polled.status}`, { status: polled.status });
|
|
118
|
+
}
|
|
119
|
+
prediction = parseJson(engine, polled.body);
|
|
120
|
+
}
|
|
121
|
+
if (prediction['status'] !== 'succeeded') {
|
|
122
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" prediction did not finish within ${REPLICATE_MAX_POLLS} polls`);
|
|
123
|
+
}
|
|
124
|
+
const output = prediction['output'];
|
|
125
|
+
if (!Array.isArray(output)) {
|
|
126
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction has no output array`);
|
|
127
|
+
}
|
|
128
|
+
const images = [];
|
|
129
|
+
for (const item of output) {
|
|
130
|
+
if (typeof item !== 'string' || item === '') {
|
|
131
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction returned a non-URL output entry`);
|
|
132
|
+
}
|
|
133
|
+
images.push({ data: await downloadImage(engine, item, `Bearer ${credential}`, deps, signal), mediaType: engine.imageMediaType });
|
|
134
|
+
}
|
|
135
|
+
if (images.length === 0) {
|
|
136
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" prediction returned no images`);
|
|
137
|
+
}
|
|
138
|
+
return images;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
/** The fal.ai adapter: POST the `fal.run` queue endpoint and download the returned image URLs. */
|
|
142
|
+
const falProvider = {
|
|
143
|
+
vocabulary: 'fal',
|
|
144
|
+
async generate(engine, request, deps, signal) {
|
|
145
|
+
const credential = await requireCredential(engine, deps);
|
|
146
|
+
const body = {
|
|
147
|
+
prompt: request.prompt,
|
|
148
|
+
image_size: engine.sizeMap[request.size ?? 'square'],
|
|
149
|
+
num_images: request.count ?? 1,
|
|
150
|
+
};
|
|
151
|
+
const response = await deps.transport.request({
|
|
152
|
+
method: 'POST',
|
|
153
|
+
url: `${engine.baseUrl}/${engine.model}`,
|
|
154
|
+
headers: { 'content-type': 'application/json', authorization: `Key ${credential}` },
|
|
155
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
156
|
+
...(signal === undefined ? {} : { signal }),
|
|
157
|
+
});
|
|
158
|
+
if (response.status === 401 || response.status === 403) {
|
|
159
|
+
throw new EngineCallError('request', 'auth', `engine "${engine.id}" rejected the credential (HTTP ${response.status})`, { status: response.status });
|
|
160
|
+
}
|
|
161
|
+
if (response.status < 200 || response.status >= 300) {
|
|
162
|
+
throw new EngineCallError('request', 'http', `engine "${engine.id}" failed with HTTP ${response.status}`, { status: response.status });
|
|
163
|
+
}
|
|
164
|
+
const payload = parseJson(engine, response.body);
|
|
165
|
+
const entries = payload['images'];
|
|
166
|
+
if (!Array.isArray(entries)) {
|
|
167
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" response has no images array`);
|
|
168
|
+
}
|
|
169
|
+
const images = [];
|
|
170
|
+
for (const raw of entries) {
|
|
171
|
+
const url = typeof raw === 'object' && raw !== null ? raw['url'] : undefined;
|
|
172
|
+
if (typeof url !== 'string' || url === '') {
|
|
173
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned an image entry without a URL`);
|
|
174
|
+
}
|
|
175
|
+
images.push({ data: await downloadImage(engine, url, `Key ${credential}`, deps, signal), mediaType: engine.imageMediaType });
|
|
176
|
+
}
|
|
177
|
+
if (images.length === 0) {
|
|
178
|
+
throw new EngineCallError('parse', 'parse', `engine "${engine.id}" returned no images`);
|
|
179
|
+
}
|
|
180
|
+
return images;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
/** The provider registry, keyed by vocabulary. */
|
|
184
|
+
export const PROVIDERS = Object.freeze({
|
|
185
|
+
openai: openaiProvider,
|
|
186
|
+
replicate: replicateProvider,
|
|
187
|
+
fal: falProvider,
|
|
188
|
+
});
|
|
189
|
+
/** Resolve the provider for one vocabulary (always present — the config gate restricts the value). */
|
|
190
|
+
export function providerFor(vocabulary) {
|
|
191
|
+
return PROVIDERS[vocabulary];
|
|
192
|
+
}
|