ima2-gen 1.1.7 → 1.1.9
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/README.md +56 -27
- package/bin/commands/annotate.js +137 -0
- package/bin/commands/annotate.ts +118 -0
- package/bin/commands/cancel.js +37 -33
- package/bin/commands/cancel.ts +45 -0
- package/bin/commands/canvas-versions.js +91 -0
- package/bin/commands/canvas-versions.ts +80 -0
- package/bin/commands/cardnews.js +293 -0
- package/bin/commands/cardnews.ts +248 -0
- package/bin/commands/comfy.js +63 -0
- package/bin/commands/comfy.ts +54 -0
- package/bin/commands/config.js +270 -0
- package/bin/commands/config.ts +265 -0
- package/bin/commands/edit.js +97 -72
- package/bin/commands/edit.ts +116 -0
- package/bin/commands/gen.js +140 -118
- package/bin/commands/gen.ts +176 -0
- package/bin/commands/history.js +164 -0
- package/bin/commands/history.ts +145 -0
- package/bin/commands/ls.js +60 -42
- package/bin/commands/ls.ts +60 -0
- package/bin/commands/metadata.js +45 -0
- package/bin/commands/metadata.ts +36 -0
- package/bin/commands/multimode.js +159 -0
- package/bin/commands/multimode.ts +146 -0
- package/bin/commands/node.js +176 -0
- package/bin/commands/node.ts +157 -0
- package/bin/commands/observability.js +201 -0
- package/bin/commands/observability.ts +176 -0
- package/bin/commands/ping.js +26 -20
- package/bin/commands/ping.ts +29 -0
- package/bin/commands/prompt.js +506 -0
- package/bin/commands/prompt.ts +421 -0
- package/bin/commands/ps.js +78 -71
- package/bin/commands/ps.ts +78 -0
- package/bin/commands/session.js +308 -0
- package/bin/commands/session.ts +265 -0
- package/bin/commands/show.js +75 -40
- package/bin/commands/show.ts +69 -0
- package/bin/ima2.js +324 -310
- package/bin/ima2.ts +444 -0
- package/bin/lib/args.js +75 -66
- package/bin/lib/args.ts +73 -0
- package/bin/lib/browser-id.js +15 -0
- package/bin/lib/browser-id.ts +16 -0
- package/bin/lib/client.js +91 -83
- package/bin/lib/client.ts +109 -0
- package/bin/lib/error-hints.js +14 -17
- package/bin/lib/error-hints.ts +23 -0
- package/bin/lib/files.js +26 -28
- package/bin/lib/files.ts +39 -0
- package/bin/lib/output.js +44 -42
- package/bin/lib/output.ts +58 -0
- package/bin/lib/platform.js +60 -56
- package/bin/lib/platform.ts +97 -0
- package/bin/lib/sse.js +73 -0
- package/bin/lib/sse.ts +73 -0
- package/bin/lib/star-prompt.js +69 -76
- package/bin/lib/star-prompt.ts +97 -0
- package/bin/lib/storage-doctor.js +34 -35
- package/bin/lib/storage-doctor.ts +38 -0
- package/config.js +147 -190
- package/config.ts +331 -0
- package/docs/API.md +48 -8
- package/docs/CLI.md +190 -0
- package/docs/FAQ.ko.md +5 -5
- package/docs/FAQ.md +5 -5
- package/docs/README.ja.md +71 -25
- package/docs/README.ko.md +61 -24
- package/docs/README.zh-CN.md +73 -27
- package/lib/assetLifecycle.js +130 -130
- package/lib/assetLifecycle.ts +142 -0
- package/lib/canvasVersionStore.js +135 -153
- package/lib/canvasVersionStore.ts +181 -0
- package/lib/cardNewsGenerator.js +127 -142
- package/lib/cardNewsGenerator.ts +162 -0
- package/lib/cardNewsJobStore.js +78 -84
- package/lib/cardNewsJobStore.ts +107 -0
- package/lib/cardNewsManifestStore.js +88 -93
- package/lib/cardNewsManifestStore.ts +112 -0
- package/lib/cardNewsPlanner.js +157 -152
- package/lib/cardNewsPlanner.ts +180 -0
- package/lib/cardNewsPlannerClient.js +101 -98
- package/lib/cardNewsPlannerClient.ts +114 -0
- package/lib/cardNewsPlannerPrompt.js +56 -56
- package/lib/cardNewsPlannerPrompt.ts +60 -0
- package/lib/cardNewsPlannerSchema.js +231 -223
- package/lib/cardNewsPlannerSchema.ts +259 -0
- package/lib/cardNewsRoleTemplateStore.js +39 -41
- package/lib/cardNewsRoleTemplateStore.ts +47 -0
- package/lib/cardNewsTemplateStore.js +171 -175
- package/lib/cardNewsTemplateStore.ts +210 -0
- package/lib/codexDetect.js +44 -47
- package/lib/codexDetect.ts +69 -0
- package/lib/comfyBridge.js +164 -184
- package/lib/comfyBridge.ts +214 -0
- package/lib/db.js +41 -51
- package/lib/db.ts +166 -0
- package/lib/errorClassify.js +62 -78
- package/lib/errorClassify.ts +100 -0
- package/lib/generationErrors.js +140 -103
- package/lib/generationErrors.ts +125 -0
- package/lib/historyList.js +149 -147
- package/lib/historyList.ts +164 -0
- package/lib/imageMetadata.js +86 -89
- package/lib/imageMetadata.ts +111 -0
- package/lib/imageMetadataStore.js +46 -51
- package/lib/imageMetadataStore.ts +67 -0
- package/lib/imageModels.js +38 -45
- package/lib/imageModels.ts +52 -0
- package/lib/inflight.js +131 -150
- package/lib/inflight.ts +204 -0
- package/lib/localImportStore.js +105 -0
- package/lib/localImportStore.ts +111 -0
- package/lib/logger.js +105 -112
- package/lib/logger.ts +150 -0
- package/lib/nodeStore.js +65 -64
- package/lib/nodeStore.ts +81 -0
- package/lib/oauthLauncher.js +61 -59
- package/lib/oauthLauncher.ts +64 -0
- package/lib/oauthNormalize.js +15 -19
- package/lib/oauthNormalize.ts +30 -0
- package/lib/oauthProxy.js +834 -832
- package/lib/oauthProxy.ts +995 -0
- package/lib/openDirectory.js +41 -40
- package/lib/openDirectory.ts +45 -0
- package/lib/pngInfo.js +18 -20
- package/lib/pngInfo.ts +26 -0
- package/lib/promptImport/curatedSources.js +135 -0
- package/lib/promptImport/curatedSources.ts +139 -0
- package/lib/promptImport/discoveryRegistry.js +218 -0
- package/lib/promptImport/discoveryRegistry.ts +236 -0
- package/lib/promptImport/errors.js +10 -10
- package/lib/promptImport/errors.ts +18 -0
- package/lib/promptImport/githubDiscovery.js +238 -0
- package/lib/promptImport/githubDiscovery.ts +248 -0
- package/lib/promptImport/githubFolder.js +302 -0
- package/lib/promptImport/githubFolder.ts +308 -0
- package/lib/promptImport/githubSource.js +194 -171
- package/lib/promptImport/githubSource.ts +239 -0
- package/lib/promptImport/gptImageHints.js +61 -0
- package/lib/promptImport/gptImageHints.ts +68 -0
- package/lib/promptImport/parsePromptCandidates.js +110 -112
- package/lib/promptImport/parsePromptCandidates.ts +153 -0
- package/lib/promptImport/promptIndex.js +230 -0
- package/lib/promptImport/promptIndex.ts +248 -0
- package/lib/promptImport/rankPromptCandidates.js +52 -0
- package/lib/promptImport/rankPromptCandidates.ts +49 -0
- package/lib/providerOptions.js +31 -0
- package/lib/providerOptions.ts +41 -0
- package/lib/referenceImageCompress.js +51 -62
- package/lib/referenceImageCompress.ts +75 -0
- package/lib/refs.js +93 -81
- package/lib/refs.ts +117 -0
- package/lib/requestLogger.js +32 -38
- package/lib/requestLogger.ts +48 -0
- package/lib/responsesImageAdapter.js +351 -0
- package/lib/responsesImageAdapter.ts +352 -0
- package/lib/runtimePorts.js +71 -73
- package/lib/runtimePorts.ts +93 -0
- package/lib/sessionStore.js +179 -230
- package/lib/sessionStore.ts +272 -0
- package/lib/storageMigration.js +247 -245
- package/lib/storageMigration.ts +284 -0
- package/lib/styleSheet.js +86 -90
- package/lib/styleSheet.ts +128 -0
- package/lib/systemTrash.js +18 -0
- package/lib/systemTrash.ts +20 -0
- package/package.json +26 -10
- package/routes/annotations.js +76 -79
- package/routes/annotations.ts +95 -0
- package/routes/canvasVersions.js +50 -54
- package/routes/canvasVersions.ts +64 -0
- package/routes/cardNews.js +158 -171
- package/routes/cardNews.ts +183 -0
- package/routes/comfy.js +23 -31
- package/routes/comfy.ts +39 -0
- package/routes/edit.js +183 -214
- package/routes/edit.ts +230 -0
- package/routes/generate.js +269 -291
- package/routes/generate.ts +309 -0
- package/routes/health.js +102 -107
- package/routes/health.ts +114 -0
- package/routes/history.js +136 -144
- package/routes/history.ts +153 -0
- package/routes/imageImport.js +33 -0
- package/routes/imageImport.ts +33 -0
- package/routes/index.js +18 -16
- package/routes/index.ts +35 -0
- package/routes/metadata.js +60 -64
- package/routes/metadata.ts +71 -0
- package/routes/multimode.js +228 -263
- package/routes/multimode.ts +280 -0
- package/routes/nodes.js +378 -424
- package/routes/nodes.ts +455 -0
- package/routes/promptImport.js +291 -152
- package/routes/promptImport.ts +354 -0
- package/routes/prompts.js +333 -360
- package/routes/prompts.ts +379 -0
- package/routes/sessions.js +277 -285
- package/routes/sessions.ts +292 -0
- package/routes/storage.js +29 -31
- package/routes/storage.ts +39 -0
- package/server.js +189 -196
- package/server.ts +235 -0
- package/ui/dist/.vite/manifest.json +101 -0
- package/ui/dist/assets/CardNewsWorkspace-BJOCey7Z.js +2 -0
- package/ui/dist/assets/NodeCanvas-BZV40eAE.css +1 -0
- package/ui/dist/assets/NodeCanvas-C3dzYNsk.js +7 -0
- package/ui/dist/assets/PromptImportDialog-Dqu1VpUh.js +2 -0
- package/ui/dist/assets/PromptImportDiscoverySection-Dg8T9X0L.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-DBaqsFO4.js +1 -0
- package/ui/dist/assets/PromptLibraryPanel-p5QqR97M.js +2 -0
- package/ui/dist/assets/SettingsWorkspace-B5bSAZ6u.js +1 -0
- package/ui/dist/assets/index-C9cXwiWE.js +25 -0
- package/ui/dist/assets/index-CGMIkZXn.css +1 -0
- package/ui/dist/assets/index-Cvld7dUZ.js +1 -0
- package/ui/dist/index.html +6 -3
- package/assets/screenshot.png +0 -0
- package/assets/screenshots/classic-generate-light.png +0 -0
- package/assets/screenshots/node-graph-branching.png +0 -0
- package/assets/screenshots/settings-oauth-generation.png +0 -0
- package/assets/screenshots/settings-workspace.png +0 -0
- package/assets/screenshots/style-sheet-editor.png +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/integrations/comfyui/ima2_gen_bridge/__pycache__/nodes.cpython-313.pyc +0 -0
- package/ui/dist/assets/index-DARPdT4Q.css +0 -1
- package/ui/dist/assets/index-ht80GMq4.js +0 -31
- package/ui/dist/assets/index-ht80GMq4.js.map +0 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { setJobPhase } from "./inflight.js";
|
|
2
|
+
import { logEvent } from "./logger.js";
|
|
3
|
+
import { classifyUpstreamError, classifyUpstreamErrorCode } from "./errorClassify.js";
|
|
4
|
+
import { compressReferenceB64ForOAuth } from "./referenceImageCompress.js";
|
|
5
|
+
import { detectImageMimeFromB64 } from "./refs.js";
|
|
6
|
+
import { AUTO_PROMPT_FIDELITY_SUFFIX, DIRECT_PROMPT_FIDELITY_SUFFIX, EDIT_DEVELOPER_PROMPT, EDIT_NO_SEARCH_DEVELOPER_PROMPT, GENERATE_DEVELOPER_PROMPT, GENERATE_NO_SEARCH_DEVELOPER_PROMPT, buildEditTextPrompt, buildMultimodeSequencePrompt, buildUserTextPrompt, waitForOAuthReady, } from "./oauthProxy.js";
|
|
7
|
+
function makeError(message, { status = 500, code = "RESPONSES_IMAGE_ERROR", cause, ...rest } = {}) {
|
|
8
|
+
const err = new Error(message);
|
|
9
|
+
err.status = status;
|
|
10
|
+
err.code = code;
|
|
11
|
+
if (cause)
|
|
12
|
+
err.cause = cause;
|
|
13
|
+
Object.assign(err, rest);
|
|
14
|
+
return err;
|
|
15
|
+
}
|
|
16
|
+
function parseOpenAIErrorBody(text) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(text);
|
|
19
|
+
const error = parsed?.error || {};
|
|
20
|
+
return {
|
|
21
|
+
message: typeof error.message === "string" && error.message ? error.message : "OpenAI request failed",
|
|
22
|
+
code: typeof error.code === "string" ? error.code : null,
|
|
23
|
+
type: typeof error.type === "string" ? error.type : null,
|
|
24
|
+
param: typeof error.param === "string" ? error.param : null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function normalizedCode(upstream) {
|
|
32
|
+
const byCode = classifyUpstreamErrorCode(upstream?.code);
|
|
33
|
+
if (byCode !== "UNKNOWN")
|
|
34
|
+
return byCode;
|
|
35
|
+
const byType = classifyUpstreamErrorCode(upstream?.type);
|
|
36
|
+
if (byType !== "UNKNOWN")
|
|
37
|
+
return byType;
|
|
38
|
+
const byMessage = classifyUpstreamError(upstream?.message);
|
|
39
|
+
return byMessage !== "UNKNOWN" ? byMessage : "RESPONSES_IMAGE_ERROR";
|
|
40
|
+
}
|
|
41
|
+
function safeUpstreamClientMessage(upstream, status) {
|
|
42
|
+
const code = normalizedCode(upstream);
|
|
43
|
+
if (code === "AUTH_API_KEY_INVALID")
|
|
44
|
+
return "API key is invalid or unavailable.";
|
|
45
|
+
if (code === "MODERATION_REFUSED")
|
|
46
|
+
return "OpenAI refused the image request for safety reasons.";
|
|
47
|
+
if (code === "INVALID_REQUEST")
|
|
48
|
+
return "OpenAI rejected the image request parameters.";
|
|
49
|
+
if (status === 401 || status === 403)
|
|
50
|
+
return "OpenAI authentication failed.";
|
|
51
|
+
if (status === 429)
|
|
52
|
+
return "OpenAI rate limited the image request.";
|
|
53
|
+
return "OpenAI rejected the image request.";
|
|
54
|
+
}
|
|
55
|
+
async function getEndpoint(ctx, provider, scope) {
|
|
56
|
+
if (provider === "api") {
|
|
57
|
+
if (!ctx?.apiKey) {
|
|
58
|
+
throw makeError("API key is required for API provider image generation", {
|
|
59
|
+
status: 401,
|
|
60
|
+
code: "API_KEY_REQUIRED",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
url: "https://api.openai.com/v1/responses",
|
|
65
|
+
headers: {
|
|
66
|
+
"Content-Type": "application/json",
|
|
67
|
+
Accept: "text/event-stream",
|
|
68
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
await waitForOAuthReady(ctx);
|
|
73
|
+
const port = ctx?.config?.oauth?.proxyPort || 10531;
|
|
74
|
+
return {
|
|
75
|
+
url: `${ctx?.oauthUrl || `http://127.0.0.1:${port}`}/v1/responses`,
|
|
76
|
+
headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function tools(webSearchEnabled, imageOptions) {
|
|
80
|
+
return [
|
|
81
|
+
...(webSearchEnabled ? [{ type: "web_search" }] : []),
|
|
82
|
+
{ type: "image_generation", ...imageOptions },
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
function normalizeRef(ref) {
|
|
86
|
+
const b64 = typeof ref === "string" ? ref : ref?.b64;
|
|
87
|
+
const detectedMime = typeof ref === "object" && ref?.detectedMime
|
|
88
|
+
? ref.detectedMime
|
|
89
|
+
: detectImageMimeFromB64(b64);
|
|
90
|
+
const declaredMime = typeof ref === "object" ? ref?.declaredMime : null;
|
|
91
|
+
const mime = ["image/png", "image/jpeg", "image/webp"].includes(detectedMime)
|
|
92
|
+
? detectedMime
|
|
93
|
+
: ["image/png", "image/jpeg", "image/webp"].includes(declaredMime)
|
|
94
|
+
? declaredMime
|
|
95
|
+
: "image/png";
|
|
96
|
+
return { type: "input_image", image_url: `data:${mime};base64,${b64}` };
|
|
97
|
+
}
|
|
98
|
+
function extractSseData(block) {
|
|
99
|
+
let eventData = "";
|
|
100
|
+
for (const line of block.split("\n")) {
|
|
101
|
+
if (line.startsWith("data: "))
|
|
102
|
+
eventData += line.slice(6);
|
|
103
|
+
}
|
|
104
|
+
return eventData;
|
|
105
|
+
}
|
|
106
|
+
function extractPartialImage(data) {
|
|
107
|
+
if (typeof data?.type !== "string" || !data.type.includes("partial"))
|
|
108
|
+
return null;
|
|
109
|
+
const item = data.item || {};
|
|
110
|
+
const b64 = data.partial_image || data.image || data.result || item.partial_image || item.image || item.result;
|
|
111
|
+
if (typeof b64 !== "string" || b64.length === 0)
|
|
112
|
+
return null;
|
|
113
|
+
const index = Number.isFinite(data.index) ? data.index : Number.isFinite(item.index) ? item.index : null;
|
|
114
|
+
return { b64, index };
|
|
115
|
+
}
|
|
116
|
+
async function parseStream(res, { requestId, scope, maxImages = 1, onPartialImage = null } = {}) {
|
|
117
|
+
const reader = res.body.getReader();
|
|
118
|
+
const decoder = new TextDecoder();
|
|
119
|
+
const images = [];
|
|
120
|
+
const eventTypes = {};
|
|
121
|
+
let buffer = "";
|
|
122
|
+
let usage = null;
|
|
123
|
+
let webSearchCalls = 0;
|
|
124
|
+
let eventCount = 0;
|
|
125
|
+
let extraIgnored = 0;
|
|
126
|
+
while (true) {
|
|
127
|
+
const { done, value } = await reader.read();
|
|
128
|
+
if (done)
|
|
129
|
+
break;
|
|
130
|
+
buffer += decoder.decode(value, { stream: true });
|
|
131
|
+
let boundary;
|
|
132
|
+
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
133
|
+
const block = buffer.slice(0, boundary);
|
|
134
|
+
buffer = buffer.slice(boundary + 2);
|
|
135
|
+
const eventData = extractSseData(block);
|
|
136
|
+
if (!eventData || eventData === "[DONE]")
|
|
137
|
+
continue;
|
|
138
|
+
let data;
|
|
139
|
+
try {
|
|
140
|
+
data = JSON.parse(eventData);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
eventCount++;
|
|
146
|
+
eventTypes[data.type || "_unknown"] = (eventTypes[data.type || "_unknown"] || 0) + 1;
|
|
147
|
+
const partial = extractPartialImage(data);
|
|
148
|
+
if (partial && typeof onPartialImage === "function")
|
|
149
|
+
onPartialImage(partial);
|
|
150
|
+
if (data.type === "response.output_item.done" && data.item?.type === "image_generation_call") {
|
|
151
|
+
if (data.item.result && images.length < maxImages) {
|
|
152
|
+
images.push({
|
|
153
|
+
b64: data.item.result,
|
|
154
|
+
revisedPrompt: typeof data.item.revised_prompt === "string" ? data.item.revised_prompt : null,
|
|
155
|
+
});
|
|
156
|
+
if (requestId)
|
|
157
|
+
setJobPhase(requestId, "decoding");
|
|
158
|
+
}
|
|
159
|
+
else if (data.item.result)
|
|
160
|
+
extraIgnored++;
|
|
161
|
+
}
|
|
162
|
+
if (data.type === "response.output_item.done" && data.item?.type === "web_search_call")
|
|
163
|
+
webSearchCalls++;
|
|
164
|
+
if (data.type === "response.completed") {
|
|
165
|
+
usage = data.response?.usage || null;
|
|
166
|
+
const wsNum = data.response?.tool_usage?.web_search?.num_requests;
|
|
167
|
+
if (typeof wsNum === "number" && wsNum > webSearchCalls)
|
|
168
|
+
webSearchCalls = wsNum;
|
|
169
|
+
}
|
|
170
|
+
if (data.type === "error") {
|
|
171
|
+
throw makeError("Responses stream returned an error", {
|
|
172
|
+
code: data.error?.code || "RESPONSES_STREAM_ERROR",
|
|
173
|
+
eventCount,
|
|
174
|
+
eventType: data.type,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
logEvent(scope, "stream_end", { requestId, events: eventCount, imageCount: images.length });
|
|
180
|
+
return { images, usage, webSearchCalls, eventCount, eventTypes, extraIgnored };
|
|
181
|
+
}
|
|
182
|
+
async function parseJson(res, maxImages) {
|
|
183
|
+
const json = await res.json();
|
|
184
|
+
const images = [];
|
|
185
|
+
let webSearchCalls = 0;
|
|
186
|
+
for (const item of json.output || []) {
|
|
187
|
+
if (item.type === "image_generation_call" && item.result && images.length < maxImages) {
|
|
188
|
+
images.push({
|
|
189
|
+
b64: item.result,
|
|
190
|
+
revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : null,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (item.type === "web_search_call")
|
|
194
|
+
webSearchCalls++;
|
|
195
|
+
}
|
|
196
|
+
return { images, usage: json.usage || null, webSearchCalls, eventCount: 0, eventTypes: {}, extraIgnored: 0 };
|
|
197
|
+
}
|
|
198
|
+
async function postResponses({ ctx, provider, scope, payload, requestId, maxImages = 1, onPartialImage = null }) {
|
|
199
|
+
const { url, headers } = await getEndpoint(ctx, provider, scope);
|
|
200
|
+
const timeoutMs = ctx?.config?.oauth?.generationTimeoutMs || 400 * 1000;
|
|
201
|
+
const controller = new AbortController();
|
|
202
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(url, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers,
|
|
207
|
+
signal: controller.signal,
|
|
208
|
+
body: JSON.stringify(payload),
|
|
209
|
+
});
|
|
210
|
+
logEvent(scope, "response", { requestId, provider, status: res.status, contentType: res.headers.get("content-type") });
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
const text = await res.text();
|
|
213
|
+
const upstream = parseOpenAIErrorBody(text);
|
|
214
|
+
if (res.status >= 400 && res.status < 500 && upstream?.message) {
|
|
215
|
+
throw makeError(safeUpstreamClientMessage(upstream, res.status), {
|
|
216
|
+
status: res.status,
|
|
217
|
+
code: normalizedCode(upstream),
|
|
218
|
+
upstreamBodyChars: text.length,
|
|
219
|
+
upstreamCode: upstream.code,
|
|
220
|
+
upstreamType: upstream.type,
|
|
221
|
+
upstreamParam: upstream.param,
|
|
222
|
+
upstreamMessageRedacted: true,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
throw makeError(`${provider === "api" ? "OpenAI API" : "OAuth proxy"} returned ${res.status}`, {
|
|
226
|
+
status: res.status,
|
|
227
|
+
upstreamBodyChars: text.length,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (requestId)
|
|
231
|
+
setJobPhase(requestId, "streaming");
|
|
232
|
+
const contentType = res.headers.get("content-type") || "";
|
|
233
|
+
return contentType.includes("text/event-stream")
|
|
234
|
+
? await parseStream(res, { requestId, scope, maxImages, onPartialImage })
|
|
235
|
+
: await parseJson(res, maxImages);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
if (err?.name === "AbortError") {
|
|
239
|
+
throw makeError("Responses image generation timed out", { status: 504, code: "RESPONSES_IMAGE_TIMEOUT", cause: err });
|
|
240
|
+
}
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
clearTimeout(timer);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export async function generateViaResponses(provider, prompt, quality, size, moderation = "low", references = [], requestId = null, mode = "auto", ctx = {}, options = {}) {
|
|
248
|
+
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
249
|
+
const referenceInputs = references.map(normalizeRef);
|
|
250
|
+
const userContent = referenceInputs.length
|
|
251
|
+
? [...referenceInputs, { type: "input_text", text: buildUserTextPrompt(prompt, mode, { webSearchEnabled }) }]
|
|
252
|
+
: buildUserTextPrompt(prompt, mode, { webSearchEnabled });
|
|
253
|
+
const result = await postResponses({
|
|
254
|
+
ctx,
|
|
255
|
+
provider,
|
|
256
|
+
scope: provider === "api" ? "api-generate" : "oauth",
|
|
257
|
+
requestId,
|
|
258
|
+
maxImages: 1,
|
|
259
|
+
onPartialImage: options.onPartialImage,
|
|
260
|
+
payload: {
|
|
261
|
+
model: options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini",
|
|
262
|
+
input: [
|
|
263
|
+
{ role: "developer", content: webSearchEnabled ? GENERATE_DEVELOPER_PROMPT : GENERATE_NO_SEARCH_DEVELOPER_PROMPT },
|
|
264
|
+
{ role: "user", content: userContent },
|
|
265
|
+
],
|
|
266
|
+
tools: tools(webSearchEnabled, { quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) }),
|
|
267
|
+
tool_choice: "required",
|
|
268
|
+
reasoning: { effort: options.reasoningEffort || "low" },
|
|
269
|
+
stream: true,
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
const image = result.images[0];
|
|
273
|
+
if (!image?.b64)
|
|
274
|
+
throw makeError("No image data received from Responses API", { code: "EMPTY_RESPONSE", eventCount: result.eventCount });
|
|
275
|
+
return { b64: image.b64, usage: result.usage, webSearchCalls: result.webSearchCalls, revisedPrompt: image.revisedPrompt };
|
|
276
|
+
}
|
|
277
|
+
export async function generateMultimodeViaResponses(provider, prompt, quality, size, moderation = "low", references = [], requestId = null, mode = "auto", ctx = {}, options = {}) {
|
|
278
|
+
const maxImages = Math.min(8, Math.max(1, Math.trunc(Number(options.maxImages) || 1)));
|
|
279
|
+
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
280
|
+
const userText = buildMultimodeSequencePrompt(mode === "direct"
|
|
281
|
+
? `${prompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`
|
|
282
|
+
: `${prompt}${webSearchEnabled ? "" : ""}${AUTO_PROMPT_FIDELITY_SUFFIX}`, maxImages, { webSearchEnabled });
|
|
283
|
+
const referenceInputs = references.map(normalizeRef);
|
|
284
|
+
const userContent = referenceInputs.length
|
|
285
|
+
? [...referenceInputs, { type: "input_text", text: userText }]
|
|
286
|
+
: userText;
|
|
287
|
+
return await postResponses({
|
|
288
|
+
ctx,
|
|
289
|
+
provider,
|
|
290
|
+
scope: provider === "api" ? "api-multimode" : "oauth-multimode",
|
|
291
|
+
requestId,
|
|
292
|
+
maxImages,
|
|
293
|
+
onPartialImage: options.onPartialImage,
|
|
294
|
+
payload: {
|
|
295
|
+
model: options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini",
|
|
296
|
+
input: [
|
|
297
|
+
{ role: "developer", content: `Create up to ${maxImages} separate image_generation_call outputs. Do not create a collage, grid, contact sheet, storyboard sheet, or multi-panel single image.` },
|
|
298
|
+
{ role: "user", content: userContent },
|
|
299
|
+
],
|
|
300
|
+
tools: tools(webSearchEnabled, { quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) }),
|
|
301
|
+
tool_choice: "required",
|
|
302
|
+
reasoning: { effort: options.reasoningEffort || "low" },
|
|
303
|
+
stream: true,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
export async function editViaResponses(provider, prompt, imageB64, quality, size, moderation = "low", mode = "auto", ctx = {}, requestId = null, options = {}) {
|
|
308
|
+
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
309
|
+
const imageForRequest = await compressReferenceB64ForOAuth(imageB64, {
|
|
310
|
+
maxB64Bytes: ctx.config?.limits?.maxRefB64Bytes,
|
|
311
|
+
force: true,
|
|
312
|
+
});
|
|
313
|
+
const referenceImages = await Promise.all((Array.isArray(options.references) ? options.references : []).map((ref) => compressReferenceB64ForOAuth(typeof ref === "string" ? ref : ref?.b64, {
|
|
314
|
+
maxB64Bytes: ctx.config?.limits?.maxRefB64Bytes,
|
|
315
|
+
force: true,
|
|
316
|
+
})));
|
|
317
|
+
const maskContent = typeof options.mask === "string" && options.mask.length > 0
|
|
318
|
+
? [
|
|
319
|
+
{ type: "input_image", image_url: `data:image/png;base64,${options.mask}` },
|
|
320
|
+
{ type: "input_text", text: "The previous image is an edit mask guide. Use it as prompt guidance for where the edit should apply; it is not a visible final image element." },
|
|
321
|
+
]
|
|
322
|
+
: [];
|
|
323
|
+
const userContent = [
|
|
324
|
+
{ type: "input_image", image_url: `data:image/jpeg;base64,${imageForRequest.b64}` },
|
|
325
|
+
...referenceImages.map(({ b64 }) => ({ type: "input_image", image_url: `data:image/jpeg;base64,${b64}` })),
|
|
326
|
+
...maskContent,
|
|
327
|
+
{ type: "input_text", text: buildEditTextPrompt(prompt, mode, { webSearchEnabled }) },
|
|
328
|
+
];
|
|
329
|
+
const result = await postResponses({
|
|
330
|
+
ctx,
|
|
331
|
+
provider,
|
|
332
|
+
scope: provider === "api" ? "api-edit" : "oauth-edit",
|
|
333
|
+
requestId,
|
|
334
|
+
maxImages: 1,
|
|
335
|
+
payload: {
|
|
336
|
+
model: options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini",
|
|
337
|
+
input: [
|
|
338
|
+
{ role: "developer", content: webSearchEnabled ? EDIT_DEVELOPER_PROMPT : EDIT_NO_SEARCH_DEVELOPER_PROMPT },
|
|
339
|
+
{ role: "user", content: userContent },
|
|
340
|
+
],
|
|
341
|
+
tools: tools(webSearchEnabled, { quality, size, moderation }),
|
|
342
|
+
tool_choice: "required",
|
|
343
|
+
reasoning: { effort: options.reasoningEffort || "low" },
|
|
344
|
+
stream: true,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
const image = result.images[0];
|
|
348
|
+
if (!image?.b64)
|
|
349
|
+
throw makeError("No image data received from Responses edit", { code: "EMPTY_RESPONSE", eventCount: result.eventCount });
|
|
350
|
+
return { b64: image.b64, usage: result.usage, revisedPrompt: image.revisedPrompt, webSearchCalls: result.webSearchCalls };
|
|
351
|
+
}
|