ima2-gen 1.1.13 → 1.1.14
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 +10 -1
- package/bin/commands/doctor.js +195 -0
- package/bin/commands/doctor.ts +202 -0
- package/bin/ima2.js +3 -105
- package/bin/ima2.ts +3 -109
- package/config.js +1 -0
- package/config.ts +5 -0
- package/docs/CLI.md +36 -0
- package/docs/FAQ.ko.md +82 -2
- package/docs/FAQ.md +85 -2
- package/docs/PROMPT_STUDIO.ko.md +111 -0
- package/docs/PROMPT_STUDIO.md +115 -0
- package/docs/README.ko.md +8 -1
- package/docs/migration/runtime-test-inventory.md +6 -1
- package/lib/agentRuntime.js +9 -2
- package/lib/agentRuntime.ts +8 -2
- package/lib/errorClassify.js +1 -1
- package/lib/errorClassify.ts +1 -1
- package/lib/generationErrors.js +121 -23
- package/lib/generationErrors.ts +100 -13
- package/lib/responsesDoctor.js +386 -0
- package/lib/responsesDoctor.ts +456 -0
- package/lib/responsesErrors.js +57 -0
- package/lib/responsesErrors.ts +83 -0
- package/lib/responsesFallback.js +72 -0
- package/lib/responsesFallback.ts +114 -0
- package/lib/responsesImageAdapter.js +121 -174
- package/lib/responsesImageAdapter.ts +136 -211
- package/lib/responsesParse.js +324 -0
- package/lib/responsesParse.ts +452 -0
- package/lib/responsesTools.js +15 -0
- package/lib/responsesTools.ts +28 -0
- package/package.json +1 -1
- package/routes/edit.js +26 -1
- package/routes/edit.ts +26 -1
- package/routes/generate.js +40 -0
- package/routes/generate.ts +47 -0
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-BJe9yxPA.js → AgentWorkspace-B6YNOZHi.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-BBLdwzYU.js → CardNewsWorkspace-EFVeg4l_.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-BSZ527J4.js → NodeCanvas-iM6yjHvO.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-Y2VygFc0.js → PromptBuilderPanel-C3GdLDCl.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-C6lFV-LL.js → PromptImportDialog-DS9vrc_w.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-D8YJFhND.js → PromptImportDiscoverySection-DHFEt_FA.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-ywfcQolW.js → PromptImportFolderSection-BQxb1zs5.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-fk4KmrGy.js → PromptLibraryPanel-NhMKVGfU.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-DL5vhAHQ.js → SettingsWorkspace-FjKjaDqj.js} +1 -1
- package/ui/dist/assets/index-BAN6lKgf.js +28 -0
- package/ui/dist/assets/{index-BLx55BOg.js → index-BbFZyM92.js} +1 -1
- package/ui/dist/assets/index-DK1faG9Z.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-ByViUJfx.css +0 -1
- package/ui/dist/assets/index-Ci36vcFD.js +0 -28
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { logEvent } from "./logger.js";
|
|
2
|
+
import type { ParsedResponsesResult } from "./responsesParse.js";
|
|
3
|
+
import type { RouteRuntimeContext } from "./runtimeContext.js";
|
|
4
|
+
import { imageToolChoice, tools } from "./responsesTools.js";
|
|
5
|
+
import { emptyResponseError } from "./responsesErrors.js";
|
|
6
|
+
import { buildUserTextPrompt } from "./oauthProxy.js";
|
|
7
|
+
|
|
8
|
+
type PostResponses = (args: {
|
|
9
|
+
ctx: RouteRuntimeContext;
|
|
10
|
+
provider: string | undefined;
|
|
11
|
+
scope: string;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
requestId?: string | null;
|
|
14
|
+
maxImages?: number;
|
|
15
|
+
signal?: AbortSignal | null;
|
|
16
|
+
}) => Promise<ParsedResponsesResult>;
|
|
17
|
+
|
|
18
|
+
export async function retryPromptOnlyJsonImage({
|
|
19
|
+
postResponses,
|
|
20
|
+
ctx,
|
|
21
|
+
provider,
|
|
22
|
+
prompt,
|
|
23
|
+
mode,
|
|
24
|
+
model,
|
|
25
|
+
quality,
|
|
26
|
+
size,
|
|
27
|
+
moderation,
|
|
28
|
+
requestId,
|
|
29
|
+
signal,
|
|
30
|
+
initial,
|
|
31
|
+
referencesDroppedOnRetry,
|
|
32
|
+
webSearchDroppedOnRetry,
|
|
33
|
+
reasoningEffort,
|
|
34
|
+
}: {
|
|
35
|
+
postResponses: PostResponses;
|
|
36
|
+
ctx: RouteRuntimeContext;
|
|
37
|
+
provider: string | undefined;
|
|
38
|
+
prompt: string | undefined;
|
|
39
|
+
mode: string;
|
|
40
|
+
model: string;
|
|
41
|
+
quality?: string;
|
|
42
|
+
size?: string;
|
|
43
|
+
moderation?: string;
|
|
44
|
+
requestId: string | null;
|
|
45
|
+
signal?: AbortSignal | null;
|
|
46
|
+
initial: ParsedResponsesResult;
|
|
47
|
+
referencesDroppedOnRetry: boolean;
|
|
48
|
+
webSearchDroppedOnRetry: boolean;
|
|
49
|
+
reasoningEffort?: string;
|
|
50
|
+
}) {
|
|
51
|
+
if (provider === "api") return null;
|
|
52
|
+
const retryKind = "prompt_only_json_image_tool";
|
|
53
|
+
const retryMeta = {
|
|
54
|
+
retryKind,
|
|
55
|
+
initialEventCount: initial.eventCount,
|
|
56
|
+
initialEventTypes: initial.eventTypes,
|
|
57
|
+
referencesDroppedOnRetry,
|
|
58
|
+
developerPromptDroppedOnRetry: true,
|
|
59
|
+
webSearchDroppedOnRetry,
|
|
60
|
+
};
|
|
61
|
+
logEvent("oauth", "retry_json", { requestId, ...retryMeta });
|
|
62
|
+
let retry: ParsedResponsesResult;
|
|
63
|
+
try {
|
|
64
|
+
retry = await postResponses({
|
|
65
|
+
ctx,
|
|
66
|
+
provider,
|
|
67
|
+
scope: "oauth-fallback",
|
|
68
|
+
requestId,
|
|
69
|
+
maxImages: 1,
|
|
70
|
+
signal,
|
|
71
|
+
payload: {
|
|
72
|
+
model,
|
|
73
|
+
input: [{ role: "user", content: buildUserTextPrompt(prompt, mode, { webSearchEnabled: false }) }],
|
|
74
|
+
tools: tools(false, { quality, size, moderation }),
|
|
75
|
+
tool_choice: imageToolChoice(true),
|
|
76
|
+
reasoning: { effort: reasoningEffort || "low" },
|
|
77
|
+
stream: false,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} catch (e) {
|
|
81
|
+
if (e && typeof e === "object") Object.assign(e, retryMeta);
|
|
82
|
+
throw e;
|
|
83
|
+
}
|
|
84
|
+
const image = retry.images[0];
|
|
85
|
+
if (image?.b64) {
|
|
86
|
+
logEvent("oauth", "retry_image", { requestId, retryKind, imageChars: image.b64.length });
|
|
87
|
+
return { b64: image.b64, usage: retry.usage, webSearchCalls: initial.webSearchCalls, revisedPrompt: image.revisedPrompt, text: retry.text, ...retryMeta };
|
|
88
|
+
}
|
|
89
|
+
logEvent("oauth", "retry_no_image", {
|
|
90
|
+
requestId,
|
|
91
|
+
retryKind,
|
|
92
|
+
fallbackEventCount: retry.eventCount,
|
|
93
|
+
fallbackImageCallSeen: retry.diagnostics.imageCallSeen,
|
|
94
|
+
fallbackImageResultCount: retry.diagnostics.imageResultCount,
|
|
95
|
+
});
|
|
96
|
+
throw emptyResponseError("No image data received from Responses API fallback", retry, {
|
|
97
|
+
provider,
|
|
98
|
+
model,
|
|
99
|
+
quality,
|
|
100
|
+
size,
|
|
101
|
+
moderation,
|
|
102
|
+
webSearchEnabled: false,
|
|
103
|
+
refsCount: 0,
|
|
104
|
+
inputImageCount: 0,
|
|
105
|
+
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
106
|
+
toolTypes: ["image_generation"],
|
|
107
|
+
toolChoiceKind: "image_generation",
|
|
108
|
+
...retryMeta,
|
|
109
|
+
fallbackEventCount: retry.eventCount,
|
|
110
|
+
fallbackEventTypes: retry.eventTypes,
|
|
111
|
+
fallbackImageCallSeen: retry.diagnostics.imageCallSeen,
|
|
112
|
+
fallbackImageResultCount: retry.diagnostics.imageResultCount,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { setJobPhase } from "./inflight.js";
|
|
2
1
|
import { logEvent } from "./logger.js";
|
|
3
2
|
import { classifyUpstreamError, classifyUpstreamErrorCode } from "./errorClassify.js";
|
|
4
3
|
import { compressReferenceB64ForOAuth } from "./referenceImageCompress.js";
|
|
5
4
|
import { detectImageMimeFromB64 } from "./refs.js";
|
|
6
5
|
import { errInfo } from "./errInfo.js";
|
|
6
|
+
import { setJobPhase } from "./inflight.js";
|
|
7
7
|
import { requireRuntimeContext } from "./runtimeContext.js";
|
|
8
|
+
import { parseJson, parseStream, safeDiagnosticLabel, } from "./responsesParse.js";
|
|
9
|
+
import { imageToolChoice, imageToolChoiceKind, tools, toolTypes, } from "./responsesTools.js";
|
|
10
|
+
import { emptyResponseError } from "./responsesErrors.js";
|
|
11
|
+
import { retryPromptOnlyJsonImage } from "./responsesFallback.js";
|
|
8
12
|
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, MULTIMODE_DEVELOPER_PROMPT, MULTIMODE_NO_SEARCH_DEVELOPER_PROMPT, buildEditTextPrompt, buildMultimodeSequencePrompt, buildUserTextPrompt, waitForOAuthReady, } from "./oauthProxy.js";
|
|
13
|
+
const RESPONSES_ERROR_MARKER = "ima2ResponsesError";
|
|
9
14
|
function makeError(message, { status = 500, code = "RESPONSES_IMAGE_ERROR", cause, ...rest } = {}) {
|
|
10
15
|
const err = new Error(message);
|
|
11
16
|
err.status = status;
|
|
@@ -13,6 +18,7 @@ function makeError(message, { status = 500, code = "RESPONSES_IMAGE_ERROR", caus
|
|
|
13
18
|
if (cause)
|
|
14
19
|
err.cause = cause;
|
|
15
20
|
Object.assign(err, rest);
|
|
21
|
+
Object.defineProperty(err, RESPONSES_ERROR_MARKER, { value: true });
|
|
16
22
|
return err;
|
|
17
23
|
}
|
|
18
24
|
function parseOpenAIErrorBody(text) {
|
|
@@ -21,9 +27,9 @@ function parseOpenAIErrorBody(text) {
|
|
|
21
27
|
const error = parsed?.error || {};
|
|
22
28
|
return {
|
|
23
29
|
message: typeof error.message === "string" && error.message ? error.message : "OpenAI request failed",
|
|
24
|
-
code:
|
|
25
|
-
type:
|
|
26
|
-
param:
|
|
30
|
+
code: safeDiagnosticLabel(error.code),
|
|
31
|
+
type: safeDiagnosticLabel(error.type),
|
|
32
|
+
param: safeDiagnosticLabel(error.param),
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
catch {
|
|
@@ -54,36 +60,56 @@ function safeUpstreamClientMessage(upstream, status) {
|
|
|
54
60
|
return "OpenAI rate limited the image request.";
|
|
55
61
|
return "OpenAI rejected the image request.";
|
|
56
62
|
}
|
|
63
|
+
function safeBaseUrl(value) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(value);
|
|
66
|
+
parsed.username = "";
|
|
67
|
+
parsed.password = "";
|
|
68
|
+
return parsed.toString().replace(/\/$/, "");
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return value.replace(/\/$/, "");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function apiAuthorizationHeader(apiKey) {
|
|
75
|
+
const key = typeof apiKey === "string" ? apiKey.trim() : "";
|
|
76
|
+
if (!key) {
|
|
77
|
+
throw makeError("API key is required for API provider image generation", {
|
|
78
|
+
status: 401,
|
|
79
|
+
code: "API_KEY_REQUIRED",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (/[\u0000-\u001f\u007f]/.test(key)) {
|
|
83
|
+
throw makeError("API key contains invalid characters.", {
|
|
84
|
+
status: 401,
|
|
85
|
+
code: "AUTH_API_KEY_INVALID",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return `Bearer ${key}`;
|
|
89
|
+
}
|
|
90
|
+
function isKnownResponsesError(value) {
|
|
91
|
+
return Boolean(value &&
|
|
92
|
+
typeof value === "object" &&
|
|
93
|
+
value.ima2ResponsesError === true);
|
|
94
|
+
}
|
|
57
95
|
async function getEndpoint(ctx, provider, _scope) {
|
|
58
96
|
if (provider === "api") {
|
|
59
|
-
if (!ctx?.apiKey) {
|
|
60
|
-
throw makeError("API key is required for API provider image generation", {
|
|
61
|
-
status: 401,
|
|
62
|
-
code: "API_KEY_REQUIRED",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
97
|
return {
|
|
66
98
|
url: "https://api.openai.com/v1/responses",
|
|
67
99
|
headers: {
|
|
68
100
|
"Content-Type": "application/json",
|
|
69
101
|
Accept: "text/event-stream",
|
|
70
|
-
Authorization:
|
|
102
|
+
Authorization: apiAuthorizationHeader(ctx.apiKey),
|
|
71
103
|
},
|
|
72
104
|
};
|
|
73
105
|
}
|
|
74
106
|
await waitForOAuthReady(ctx);
|
|
75
107
|
const port = ctx?.config?.oauth?.proxyPort || 10531;
|
|
76
108
|
return {
|
|
77
|
-
url: `${ctx?.oauthUrl || `http://127.0.0.1:${port}`}/v1/responses`,
|
|
109
|
+
url: `${safeBaseUrl(ctx?.oauthUrl || `http://127.0.0.1:${port}`)}/v1/responses`,
|
|
78
110
|
headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
|
|
79
111
|
};
|
|
80
112
|
}
|
|
81
|
-
function tools(webSearchEnabled, imageOptions) {
|
|
82
|
-
return [
|
|
83
|
-
...(webSearchEnabled ? [{ type: "web_search" }] : []),
|
|
84
|
-
{ type: "image_generation", ...imageOptions },
|
|
85
|
-
];
|
|
86
|
-
}
|
|
87
113
|
function normalizeRef(ref) {
|
|
88
114
|
const b64 = typeof ref === "string" ? ref : ref?.b64;
|
|
89
115
|
const detectedMime = typeof ref === "object" && ref?.detectedMime
|
|
@@ -97,149 +123,6 @@ function normalizeRef(ref) {
|
|
|
97
123
|
: "image/png";
|
|
98
124
|
return { type: "input_image", image_url: `data:${mime};base64,${b64}` };
|
|
99
125
|
}
|
|
100
|
-
function extractSseData(block) {
|
|
101
|
-
let eventData = "";
|
|
102
|
-
for (const line of block.split("\n")) {
|
|
103
|
-
if (line.startsWith("data: "))
|
|
104
|
-
eventData += line.slice(6);
|
|
105
|
-
}
|
|
106
|
-
return eventData;
|
|
107
|
-
}
|
|
108
|
-
function extractPartialImage(data) {
|
|
109
|
-
if (typeof data?.type !== "string" || !data.type.includes("partial"))
|
|
110
|
-
return null;
|
|
111
|
-
const item = data.item || {};
|
|
112
|
-
const b64 = data.partial_image || data.image || data.result || item.partial_image || item.image || item.result;
|
|
113
|
-
if (typeof b64 !== "string" || b64.length === 0)
|
|
114
|
-
return null;
|
|
115
|
-
const index = Number.isFinite(data.index) ? data.index : Number.isFinite(item.index) ? item.index : null;
|
|
116
|
-
return { b64, index };
|
|
117
|
-
}
|
|
118
|
-
function extractTextDelta(data) {
|
|
119
|
-
if (data.type === "response.output_text.delta" && typeof data.delta === "string")
|
|
120
|
-
return data.delta;
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
function extractFinalText(data) {
|
|
124
|
-
if (data.type === "response.output_text.done" && typeof data.text === "string")
|
|
125
|
-
return cleanTextOutput(data.text);
|
|
126
|
-
if (data.type === "response.output_item.done" && data.item?.type === "message") {
|
|
127
|
-
return extractJsonItemText(data.item);
|
|
128
|
-
}
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
function extractJsonItemText(item) {
|
|
132
|
-
if (item.type === "output_text" && typeof item.text === "string")
|
|
133
|
-
return cleanTextOutput(item.text);
|
|
134
|
-
if (!Array.isArray(item.content))
|
|
135
|
-
return null;
|
|
136
|
-
const text = item.content
|
|
137
|
-
.filter((part) => part.type === "output_text" && typeof part.text === "string")
|
|
138
|
-
.map((part) => part.text)
|
|
139
|
-
.join("\n\n");
|
|
140
|
-
return cleanTextOutput(text);
|
|
141
|
-
}
|
|
142
|
-
function cleanTextOutput(value) {
|
|
143
|
-
const trimmed = value.trim();
|
|
144
|
-
return trimmed ? trimmed.slice(0, 4_000) : null;
|
|
145
|
-
}
|
|
146
|
-
async function parseStream(res, { requestId, scope, maxImages = 1, onPartialImage = null, onFinalImage = null, }) {
|
|
147
|
-
const reader = res.body.getReader();
|
|
148
|
-
const decoder = new TextDecoder();
|
|
149
|
-
const images = [];
|
|
150
|
-
const eventTypes = {};
|
|
151
|
-
let buffer = "";
|
|
152
|
-
let usage = null;
|
|
153
|
-
let textOutput = "";
|
|
154
|
-
let finalTextOutput = null;
|
|
155
|
-
let webSearchCalls = 0;
|
|
156
|
-
let eventCount = 0;
|
|
157
|
-
let extraIgnored = 0;
|
|
158
|
-
while (true) {
|
|
159
|
-
const { done, value } = await reader.read();
|
|
160
|
-
if (done)
|
|
161
|
-
break;
|
|
162
|
-
buffer += decoder.decode(value, { stream: true });
|
|
163
|
-
let boundary;
|
|
164
|
-
while ((boundary = buffer.indexOf("\n\n")) !== -1) {
|
|
165
|
-
const block = buffer.slice(0, boundary);
|
|
166
|
-
buffer = buffer.slice(boundary + 2);
|
|
167
|
-
const eventData = extractSseData(block);
|
|
168
|
-
if (!eventData || eventData === "[DONE]")
|
|
169
|
-
continue;
|
|
170
|
-
let data;
|
|
171
|
-
try {
|
|
172
|
-
data = JSON.parse(eventData);
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
eventCount++;
|
|
178
|
-
eventTypes[data.type || "_unknown"] = (eventTypes[data.type || "_unknown"] || 0) + 1;
|
|
179
|
-
const delta = extractTextDelta(data);
|
|
180
|
-
if (delta)
|
|
181
|
-
textOutput += delta;
|
|
182
|
-
const finalText = extractFinalText(data);
|
|
183
|
-
if (finalText)
|
|
184
|
-
finalTextOutput = finalText;
|
|
185
|
-
const partial = extractPartialImage(data);
|
|
186
|
-
if (partial && typeof onPartialImage === "function")
|
|
187
|
-
onPartialImage(partial);
|
|
188
|
-
if (data.type === "response.output_item.done" && data.item?.type === "image_generation_call") {
|
|
189
|
-
if (data.item.result && images.length < maxImages) {
|
|
190
|
-
const image = {
|
|
191
|
-
b64: data.item.result,
|
|
192
|
-
revisedPrompt: typeof data.item.revised_prompt === "string" ? data.item.revised_prompt : null,
|
|
193
|
-
};
|
|
194
|
-
const index = images.length;
|
|
195
|
-
images.push(image);
|
|
196
|
-
if (requestId)
|
|
197
|
-
setJobPhase(requestId, "decoding");
|
|
198
|
-
await onFinalImage?.(image, index);
|
|
199
|
-
}
|
|
200
|
-
else if (data.item.result)
|
|
201
|
-
extraIgnored++;
|
|
202
|
-
}
|
|
203
|
-
if (data.type === "response.output_item.done" && data.item?.type === "web_search_call")
|
|
204
|
-
webSearchCalls++;
|
|
205
|
-
if (data.type === "response.completed") {
|
|
206
|
-
usage = data.response?.usage || null;
|
|
207
|
-
const wsNum = data.response?.tool_usage?.web_search?.num_requests;
|
|
208
|
-
if (typeof wsNum === "number" && wsNum > webSearchCalls)
|
|
209
|
-
webSearchCalls = wsNum;
|
|
210
|
-
}
|
|
211
|
-
if (data.type === "error") {
|
|
212
|
-
throw makeError("Responses stream returned an error", {
|
|
213
|
-
code: data.error?.code || "RESPONSES_STREAM_ERROR",
|
|
214
|
-
eventCount,
|
|
215
|
-
eventType: data.type,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
logEvent(scope, "stream_end", { requestId, events: eventCount, imageCount: images.length });
|
|
221
|
-
return { images, usage, webSearchCalls, eventCount, eventTypes, extraIgnored, text: finalTextOutput ?? cleanTextOutput(textOutput) };
|
|
222
|
-
}
|
|
223
|
-
async function parseJson(res, maxImages) {
|
|
224
|
-
const json = await res.json();
|
|
225
|
-
const images = [];
|
|
226
|
-
const textParts = [];
|
|
227
|
-
let webSearchCalls = 0;
|
|
228
|
-
for (const item of json.output || []) {
|
|
229
|
-
if (item.type === "image_generation_call" && item.result && images.length < maxImages) {
|
|
230
|
-
images.push({
|
|
231
|
-
b64: item.result,
|
|
232
|
-
revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : null,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
if (item.type === "web_search_call")
|
|
236
|
-
webSearchCalls++;
|
|
237
|
-
const itemText = extractJsonItemText(item);
|
|
238
|
-
if (itemText)
|
|
239
|
-
textParts.push(itemText);
|
|
240
|
-
}
|
|
241
|
-
return { images, usage: json.usage || null, webSearchCalls, eventCount: 0, eventTypes: {}, extraIgnored: 0, text: cleanTextOutput(textParts.join("\n\n")) };
|
|
242
|
-
}
|
|
243
126
|
function combineAbortSignals(signals) {
|
|
244
127
|
if (signals.length === 1)
|
|
245
128
|
return signals[0];
|
|
@@ -307,7 +190,14 @@ async function postResponses({ ctx, provider, scope, payload, requestId, maxImag
|
|
|
307
190
|
}
|
|
308
191
|
throw makeError("Responses image generation timed out", { status: 504, code: "RESPONSES_IMAGE_TIMEOUT", cause: err.raw });
|
|
309
192
|
}
|
|
310
|
-
|
|
193
|
+
if (isKnownResponsesError(err.raw))
|
|
194
|
+
throw err.raw;
|
|
195
|
+
throw makeError("Responses request failed before receiving a response", {
|
|
196
|
+
status: 502,
|
|
197
|
+
code: "NETWORK_FAILED",
|
|
198
|
+
errorName: err.name,
|
|
199
|
+
upstreamMessageRedacted: true,
|
|
200
|
+
});
|
|
311
201
|
}
|
|
312
202
|
finally {
|
|
313
203
|
clearTimeout(timer);
|
|
@@ -315,7 +205,11 @@ async function postResponses({ ctx, provider, scope, payload, requestId, maxImag
|
|
|
315
205
|
}
|
|
316
206
|
export async function generateViaResponses(provider, prompt, quality, size, moderation = "low", references = [], requestId = null, mode = "auto", ctxRaw = {}, options = {}) {
|
|
317
207
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
208
|
+
const model = options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini";
|
|
318
209
|
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
210
|
+
const requestTools = tools(webSearchEnabled, { quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) });
|
|
211
|
+
const toolChoice = imageToolChoice(options.forceImageToolChoice ?? ctx.config?.oauth?.forceImageToolChoice !== false);
|
|
212
|
+
const toolChoiceKind = imageToolChoiceKind(toolChoice);
|
|
319
213
|
const referenceInputs = references.map(normalizeRef);
|
|
320
214
|
const userContent = referenceInputs.length
|
|
321
215
|
? [...referenceInputs, { type: "input_text", text: buildUserTextPrompt(prompt, mode, { webSearchEnabled }) }]
|
|
@@ -330,26 +224,62 @@ export async function generateViaResponses(provider, prompt, quality, size, mode
|
|
|
330
224
|
onPartialImage: options.onPartialImage,
|
|
331
225
|
onFinalImage: options.onFinalImage,
|
|
332
226
|
payload: {
|
|
333
|
-
model
|
|
227
|
+
model,
|
|
334
228
|
input: [
|
|
335
229
|
{ role: "developer", content: webSearchEnabled ? GENERATE_DEVELOPER_PROMPT : GENERATE_NO_SEARCH_DEVELOPER_PROMPT },
|
|
336
230
|
{ role: "user", content: userContent },
|
|
337
231
|
],
|
|
338
|
-
tools:
|
|
339
|
-
tool_choice:
|
|
232
|
+
tools: requestTools,
|
|
233
|
+
tool_choice: toolChoice,
|
|
340
234
|
reasoning: { effort: options.reasoningEffort || "low" },
|
|
341
235
|
stream: true,
|
|
342
236
|
},
|
|
343
237
|
});
|
|
344
238
|
const image = result.images[0];
|
|
345
|
-
if (!image?.b64)
|
|
346
|
-
|
|
239
|
+
if (!image?.b64) {
|
|
240
|
+
if (options.allowPromptOnlyOAuthFallback === true) {
|
|
241
|
+
const fallback = await retryPromptOnlyJsonImage({
|
|
242
|
+
postResponses,
|
|
243
|
+
ctx,
|
|
244
|
+
provider,
|
|
245
|
+
prompt,
|
|
246
|
+
mode,
|
|
247
|
+
model,
|
|
248
|
+
quality,
|
|
249
|
+
size,
|
|
250
|
+
moderation,
|
|
251
|
+
requestId,
|
|
252
|
+
signal: options.signal,
|
|
253
|
+
initial: result,
|
|
254
|
+
referencesDroppedOnRetry: referenceInputs.length > 0,
|
|
255
|
+
webSearchDroppedOnRetry: webSearchEnabled,
|
|
256
|
+
reasoningEffort: options.reasoningEffort,
|
|
257
|
+
});
|
|
258
|
+
if (fallback)
|
|
259
|
+
return fallback;
|
|
260
|
+
}
|
|
261
|
+
throw emptyResponseError("No image data received from Responses API", result, {
|
|
262
|
+
provider,
|
|
263
|
+
model,
|
|
264
|
+
quality,
|
|
265
|
+
size,
|
|
266
|
+
moderation,
|
|
267
|
+
webSearchEnabled,
|
|
268
|
+
refsCount: referenceInputs.length,
|
|
269
|
+
inputImageCount: referenceInputs.length,
|
|
270
|
+
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
271
|
+
toolTypes: toolTypes(requestTools),
|
|
272
|
+
toolChoiceKind,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
347
275
|
return { b64: image.b64, usage: result.usage, webSearchCalls: result.webSearchCalls, revisedPrompt: image.revisedPrompt, text: result.text };
|
|
348
276
|
}
|
|
349
277
|
export async function generateMultimodeViaResponses(provider, prompt, quality, size, moderation = "low", references = [], requestId = null, mode = "auto", ctxRaw = {}, options = {}) {
|
|
350
278
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
351
279
|
const maxImages = Math.min(8, Math.max(1, Math.trunc(Number(options.maxImages) || 1)));
|
|
280
|
+
const model = options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini";
|
|
352
281
|
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
282
|
+
const requestTools = tools(webSearchEnabled, { quality, size, moderation, ...(options.partialImages ? { partial_images: options.partialImages } : {}) });
|
|
353
283
|
const userText = buildMultimodeSequencePrompt(mode === "direct"
|
|
354
284
|
? `${prompt}${DIRECT_PROMPT_FIDELITY_SUFFIX}`
|
|
355
285
|
: `${prompt}${webSearchEnabled ? "" : ""}${AUTO_PROMPT_FIDELITY_SUFFIX}`, maxImages, { webSearchEnabled });
|
|
@@ -367,12 +297,12 @@ export async function generateMultimodeViaResponses(provider, prompt, quality, s
|
|
|
367
297
|
onPartialImage: options.onPartialImage,
|
|
368
298
|
onFinalImage: options.onFinalImage,
|
|
369
299
|
payload: {
|
|
370
|
-
model
|
|
300
|
+
model,
|
|
371
301
|
input: [
|
|
372
302
|
{ role: "developer", content: webSearchEnabled ? MULTIMODE_DEVELOPER_PROMPT : MULTIMODE_NO_SEARCH_DEVELOPER_PROMPT },
|
|
373
303
|
{ role: "user", content: userContent },
|
|
374
304
|
],
|
|
375
|
-
tools:
|
|
305
|
+
tools: requestTools,
|
|
376
306
|
tool_choice: "required",
|
|
377
307
|
reasoning: { effort: options.reasoningEffort || "low" },
|
|
378
308
|
stream: true,
|
|
@@ -381,7 +311,11 @@ export async function generateMultimodeViaResponses(provider, prompt, quality, s
|
|
|
381
311
|
}
|
|
382
312
|
export async function editViaResponses(provider, prompt, imageB64, quality, size, moderation = "low", mode = "auto", ctxRaw = {}, requestId = null, options = {}) {
|
|
383
313
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
314
|
+
const model = options.model || ctx.config?.imageModels?.default || "gpt-5.4-mini";
|
|
384
315
|
const webSearchEnabled = options.webSearchEnabled !== false && options.searchMode !== "off";
|
|
316
|
+
const requestTools = tools(webSearchEnabled, { quality, size, moderation });
|
|
317
|
+
const toolChoice = imageToolChoice(options.forceImageToolChoice ?? ctx.config?.oauth?.forceImageToolChoice !== false);
|
|
318
|
+
const toolChoiceKind = imageToolChoiceKind(toolChoice);
|
|
385
319
|
const imageForRequest = await compressReferenceB64ForOAuth(imageB64, {
|
|
386
320
|
maxB64Bytes: ctx.config?.limits?.maxRefB64Bytes,
|
|
387
321
|
force: true,
|
|
@@ -410,19 +344,32 @@ export async function editViaResponses(provider, prompt, imageB64, quality, size
|
|
|
410
344
|
maxImages: 1,
|
|
411
345
|
signal: options.signal,
|
|
412
346
|
payload: {
|
|
413
|
-
model
|
|
347
|
+
model,
|
|
414
348
|
input: [
|
|
415
349
|
{ role: "developer", content: webSearchEnabled ? EDIT_DEVELOPER_PROMPT : EDIT_NO_SEARCH_DEVELOPER_PROMPT },
|
|
416
350
|
{ role: "user", content: userContent },
|
|
417
351
|
],
|
|
418
|
-
tools:
|
|
419
|
-
tool_choice:
|
|
352
|
+
tools: requestTools,
|
|
353
|
+
tool_choice: toolChoice,
|
|
420
354
|
reasoning: { effort: options.reasoningEffort || "low" },
|
|
421
355
|
stream: true,
|
|
422
356
|
},
|
|
423
357
|
});
|
|
424
358
|
const image = result.images[0];
|
|
425
|
-
if (!image?.b64)
|
|
426
|
-
throw
|
|
359
|
+
if (!image?.b64) {
|
|
360
|
+
throw emptyResponseError("No image data received from Responses edit", result, {
|
|
361
|
+
provider,
|
|
362
|
+
model,
|
|
363
|
+
quality,
|
|
364
|
+
size,
|
|
365
|
+
moderation,
|
|
366
|
+
webSearchEnabled,
|
|
367
|
+
refsCount: referenceImages.length,
|
|
368
|
+
inputImageCount: 1 + referenceImages.length + (maskContent.length ? 1 : 0),
|
|
369
|
+
promptChars: typeof prompt === "string" ? prompt.length : 0,
|
|
370
|
+
toolTypes: toolTypes(requestTools),
|
|
371
|
+
toolChoiceKind,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
427
374
|
return { b64: image.b64, usage: result.usage, revisedPrompt: image.revisedPrompt, webSearchCalls: result.webSearchCalls };
|
|
428
375
|
}
|