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,324 @@
|
|
|
1
|
+
import { setJobPhase } from "./inflight.js";
|
|
2
|
+
import { logEvent } from "./logger.js";
|
|
3
|
+
function createState() {
|
|
4
|
+
return {
|
|
5
|
+
images: [],
|
|
6
|
+
eventTypes: {},
|
|
7
|
+
outputItemSummary: [],
|
|
8
|
+
usage: null,
|
|
9
|
+
textOutput: "",
|
|
10
|
+
finalTextOutput: null,
|
|
11
|
+
webSearchCalls: 0,
|
|
12
|
+
eventCount: 0,
|
|
13
|
+
extraIgnored: 0,
|
|
14
|
+
chunkCount: 0,
|
|
15
|
+
bytesRead: 0,
|
|
16
|
+
maxChunkBytes: 0,
|
|
17
|
+
lfBoundaryCount: 0,
|
|
18
|
+
crlfBoundaryCount: 0,
|
|
19
|
+
parseSkipCount: 0,
|
|
20
|
+
finalBufferChars: 0,
|
|
21
|
+
sawDoneSentinel: false,
|
|
22
|
+
sawResponseCompleted: false,
|
|
23
|
+
imageCallSeen: false,
|
|
24
|
+
imageCallCompleted: false,
|
|
25
|
+
imageCallFailed: false,
|
|
26
|
+
imageResultCount: 0,
|
|
27
|
+
webSearchCallSeen: false,
|
|
28
|
+
messageOutputSeen: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const MAX_DIAGNOSTIC_LABEL_CHARS = 120;
|
|
32
|
+
const UNSAFE_DIAGNOSTIC_LABEL = /(bearer\s+|sk-[a-z0-9_-]{4,}|data:image\/|https?:\/\/|[a-z][a-z0-9+.-]*:\/\/|@|[\r\n])/i;
|
|
33
|
+
const SAFE_DIAGNOSTIC_LABEL = /^[A-Za-z0-9_.:[\]-]+$/;
|
|
34
|
+
export function safeDiagnosticLabel(value, fallback = null) {
|
|
35
|
+
if (typeof value !== "string" || value.length === 0)
|
|
36
|
+
return fallback;
|
|
37
|
+
const trimmed = value.slice(0, MAX_DIAGNOSTIC_LABEL_CHARS);
|
|
38
|
+
if (UNSAFE_DIAGNOSTIC_LABEL.test(trimmed))
|
|
39
|
+
return "_redacted";
|
|
40
|
+
if (!SAFE_DIAGNOSTIC_LABEL.test(trimmed))
|
|
41
|
+
return "_redacted";
|
|
42
|
+
return trimmed;
|
|
43
|
+
}
|
|
44
|
+
function extractSseData(block) {
|
|
45
|
+
let eventData = "";
|
|
46
|
+
for (const rawLine of block.split(/\r?\n/)) {
|
|
47
|
+
const line = rawLine.replace(/\r$/, "");
|
|
48
|
+
if (line.startsWith("data:"))
|
|
49
|
+
eventData += line.slice(5).trimStart();
|
|
50
|
+
}
|
|
51
|
+
return eventData;
|
|
52
|
+
}
|
|
53
|
+
function nextSseBlock(buffer) {
|
|
54
|
+
const match = /\r?\n\r?\n/.exec(buffer);
|
|
55
|
+
if (!match)
|
|
56
|
+
return null;
|
|
57
|
+
return {
|
|
58
|
+
block: buffer.slice(0, match.index),
|
|
59
|
+
rest: buffer.slice(match.index + match[0].length),
|
|
60
|
+
delimiter: match[0],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function extractPartialImage(data) {
|
|
64
|
+
if (typeof data?.type !== "string" || !data.type.includes("partial"))
|
|
65
|
+
return null;
|
|
66
|
+
const item = data.item || {};
|
|
67
|
+
const b64 = data.partial_image_b64 ||
|
|
68
|
+
data.partial_image ||
|
|
69
|
+
data.image ||
|
|
70
|
+
data.result ||
|
|
71
|
+
item.partial_image_b64 ||
|
|
72
|
+
item.partial_image ||
|
|
73
|
+
item.image ||
|
|
74
|
+
item.result;
|
|
75
|
+
if (typeof b64 !== "string" || b64.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const index = typeof data.partial_image_index === "number" && Number.isFinite(data.partial_image_index)
|
|
78
|
+
? data.partial_image_index
|
|
79
|
+
: typeof data.index === "number" && Number.isFinite(data.index)
|
|
80
|
+
? data.index
|
|
81
|
+
: typeof item.partial_image_index === "number" && Number.isFinite(item.partial_image_index)
|
|
82
|
+
? item.partial_image_index
|
|
83
|
+
: typeof item.index === "number" && Number.isFinite(item.index)
|
|
84
|
+
? item.index
|
|
85
|
+
: null;
|
|
86
|
+
return { b64, index };
|
|
87
|
+
}
|
|
88
|
+
function extractTextDelta(data) {
|
|
89
|
+
if (data.type === "response.output_text.delta" && typeof data.delta === "string")
|
|
90
|
+
return data.delta;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function extractFinalText(data) {
|
|
94
|
+
if (data.type === "response.output_text.done" && typeof data.text === "string")
|
|
95
|
+
return cleanTextOutput(data.text);
|
|
96
|
+
if (data.type === "response.output_item.done" && data.item?.type === "message") {
|
|
97
|
+
return extractJsonItemText(data.item);
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function extractJsonItemText(item) {
|
|
102
|
+
if (item.type === "output_text" && typeof item.text === "string")
|
|
103
|
+
return cleanTextOutput(item.text);
|
|
104
|
+
if (!Array.isArray(item.content))
|
|
105
|
+
return null;
|
|
106
|
+
const text = item.content
|
|
107
|
+
.filter((part) => part.type === "output_text" && typeof part.text === "string")
|
|
108
|
+
.map((part) => part.text)
|
|
109
|
+
.join("\n\n");
|
|
110
|
+
return cleanTextOutput(text);
|
|
111
|
+
}
|
|
112
|
+
function cleanTextOutput(value) {
|
|
113
|
+
const trimmed = value.trim();
|
|
114
|
+
return trimmed ? trimmed.slice(0, 4_000) : null;
|
|
115
|
+
}
|
|
116
|
+
function summarizeItem(eventType, item) {
|
|
117
|
+
const result = typeof item.result === "string" ? item.result : "";
|
|
118
|
+
const revised = typeof item.revised_prompt === "string" ? item.revised_prompt : "";
|
|
119
|
+
return {
|
|
120
|
+
eventType: safeDiagnosticLabel(eventType, "_unknown") || "_unknown",
|
|
121
|
+
itemType: safeDiagnosticLabel(item.type),
|
|
122
|
+
status: safeDiagnosticLabel(item.status),
|
|
123
|
+
hasResult: result.length > 0,
|
|
124
|
+
resultChars: result.length,
|
|
125
|
+
revisedPromptChars: revised.length,
|
|
126
|
+
hasError: Boolean(item.error),
|
|
127
|
+
errorCode: safeDiagnosticLabel(item.error?.code),
|
|
128
|
+
errorType: safeDiagnosticLabel(item.error?.type),
|
|
129
|
+
errorParam: safeDiagnosticLabel(item.error?.param),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function recordOutputItem(state, eventType, item) {
|
|
133
|
+
if (!item)
|
|
134
|
+
return;
|
|
135
|
+
const summary = summarizeItem(eventType, item);
|
|
136
|
+
state.outputItemSummary.push(summary);
|
|
137
|
+
if (item.type === "image_generation_call") {
|
|
138
|
+
state.imageCallSeen = true;
|
|
139
|
+
state.imageCallCompleted = state.imageCallCompleted || eventType === "response.output_item.done" || item.status === "completed";
|
|
140
|
+
state.imageCallFailed = state.imageCallFailed || item.status === "failed" || Boolean(item.error);
|
|
141
|
+
if (summary.hasResult)
|
|
142
|
+
state.imageResultCount++;
|
|
143
|
+
}
|
|
144
|
+
if (item.type === "web_search_call")
|
|
145
|
+
state.webSearchCallSeen = true;
|
|
146
|
+
if (item.type === "message")
|
|
147
|
+
state.messageOutputSeen = true;
|
|
148
|
+
}
|
|
149
|
+
function diagnosticsFromState(state) {
|
|
150
|
+
const outputTextChars = (state.finalTextOutput ?? cleanTextOutput(state.textOutput) ?? "").length;
|
|
151
|
+
return {
|
|
152
|
+
eventTypes: state.eventTypes,
|
|
153
|
+
streamStats: {
|
|
154
|
+
chunkCount: state.chunkCount,
|
|
155
|
+
bytesRead: state.bytesRead,
|
|
156
|
+
maxChunkBytes: state.maxChunkBytes,
|
|
157
|
+
lfBoundaryCount: state.lfBoundaryCount,
|
|
158
|
+
crlfBoundaryCount: state.crlfBoundaryCount,
|
|
159
|
+
parseSkipCount: state.parseSkipCount,
|
|
160
|
+
finalBufferChars: state.finalBufferChars,
|
|
161
|
+
sawDoneSentinel: state.sawDoneSentinel,
|
|
162
|
+
sawResponseCompleted: state.sawResponseCompleted,
|
|
163
|
+
},
|
|
164
|
+
outputItemSummary: state.outputItemSummary,
|
|
165
|
+
imageCallSeen: state.imageCallSeen,
|
|
166
|
+
imageCallCompleted: state.imageCallCompleted,
|
|
167
|
+
imageCallFailed: state.imageCallFailed,
|
|
168
|
+
imageResultCount: state.imageResultCount,
|
|
169
|
+
webSearchCallSeen: state.webSearchCallSeen,
|
|
170
|
+
messageOutputSeen: state.messageOutputSeen,
|
|
171
|
+
outputTextChars,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function resultFromState(state) {
|
|
175
|
+
const text = state.finalTextOutput ?? cleanTextOutput(state.textOutput);
|
|
176
|
+
return {
|
|
177
|
+
images: state.images,
|
|
178
|
+
usage: state.usage,
|
|
179
|
+
webSearchCalls: state.webSearchCalls,
|
|
180
|
+
eventCount: state.eventCount,
|
|
181
|
+
eventTypes: state.eventTypes,
|
|
182
|
+
extraIgnored: state.extraIgnored,
|
|
183
|
+
text,
|
|
184
|
+
diagnostics: diagnosticsFromState(state),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function makeStreamError(message, code, eventCount, eventType) {
|
|
188
|
+
const err = new Error(message);
|
|
189
|
+
err.code = "RESPONSES_STREAM_ERROR";
|
|
190
|
+
err.upstreamCode = code;
|
|
191
|
+
err.status = 502;
|
|
192
|
+
err.eventCount = eventCount;
|
|
193
|
+
err.eventType = eventType;
|
|
194
|
+
Object.defineProperty(err, "ima2ResponsesError", { value: true });
|
|
195
|
+
return err;
|
|
196
|
+
}
|
|
197
|
+
async function appendFinalImageFromItem(state, item, maxImages, requestId, onFinalImage) {
|
|
198
|
+
if (item.type !== "image_generation_call" || typeof item.result !== "string" || !item.result)
|
|
199
|
+
return;
|
|
200
|
+
if (state.images.some((image) => image.b64 === item.result))
|
|
201
|
+
return;
|
|
202
|
+
if (state.images.length < maxImages) {
|
|
203
|
+
const image = {
|
|
204
|
+
b64: item.result,
|
|
205
|
+
revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : null,
|
|
206
|
+
};
|
|
207
|
+
const index = state.images.length;
|
|
208
|
+
state.images.push(image);
|
|
209
|
+
if (requestId)
|
|
210
|
+
setJobPhase(requestId, "decoding");
|
|
211
|
+
await onFinalImage?.(image, index);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
state.extraIgnored++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
export async function parseStream(res, { requestId, scope, maxImages = 1, onPartialImage = null, onFinalImage = null, }) {
|
|
218
|
+
const reader = res.body.getReader();
|
|
219
|
+
const decoder = new TextDecoder();
|
|
220
|
+
const state = createState();
|
|
221
|
+
let buffer = "";
|
|
222
|
+
while (true) {
|
|
223
|
+
const { done, value } = await reader.read();
|
|
224
|
+
if (done)
|
|
225
|
+
break;
|
|
226
|
+
state.chunkCount++;
|
|
227
|
+
state.bytesRead += value.byteLength;
|
|
228
|
+
state.maxChunkBytes = Math.max(state.maxChunkBytes, value.byteLength);
|
|
229
|
+
buffer += decoder.decode(value, { stream: true });
|
|
230
|
+
let next;
|
|
231
|
+
while ((next = nextSseBlock(buffer)) !== null) {
|
|
232
|
+
const block = next.block;
|
|
233
|
+
buffer = next.rest;
|
|
234
|
+
if (next.delimiter.includes("\r\n"))
|
|
235
|
+
state.crlfBoundaryCount++;
|
|
236
|
+
else
|
|
237
|
+
state.lfBoundaryCount++;
|
|
238
|
+
const eventData = extractSseData(block);
|
|
239
|
+
if (eventData === "[DONE]") {
|
|
240
|
+
state.sawDoneSentinel = true;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (!eventData)
|
|
244
|
+
continue;
|
|
245
|
+
let data;
|
|
246
|
+
try {
|
|
247
|
+
data = JSON.parse(eventData);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
state.parseSkipCount++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
state.eventCount++;
|
|
254
|
+
const eventType = safeDiagnosticLabel(data.type, "_unknown") || "_unknown";
|
|
255
|
+
state.eventTypes[eventType] = (state.eventTypes[eventType] || 0) + 1;
|
|
256
|
+
const delta = extractTextDelta(data);
|
|
257
|
+
if (delta)
|
|
258
|
+
state.textOutput += delta;
|
|
259
|
+
const finalText = extractFinalText(data);
|
|
260
|
+
if (finalText)
|
|
261
|
+
state.finalTextOutput = finalText;
|
|
262
|
+
if (finalText)
|
|
263
|
+
state.messageOutputSeen = true;
|
|
264
|
+
const partial = extractPartialImage(data);
|
|
265
|
+
if (partial && typeof onPartialImage === "function")
|
|
266
|
+
onPartialImage(partial);
|
|
267
|
+
if (data.type === "response.output_item.done")
|
|
268
|
+
recordOutputItem(state, data.type, data.item);
|
|
269
|
+
if (data.type === "response.output_item.done" && data.item?.type === "image_generation_call") {
|
|
270
|
+
await appendFinalImageFromItem(state, data.item, maxImages, requestId, onFinalImage);
|
|
271
|
+
}
|
|
272
|
+
if (data.type === "response.output_item.done" && data.item?.type === "web_search_call")
|
|
273
|
+
state.webSearchCalls++;
|
|
274
|
+
if (data.type === "response.completed") {
|
|
275
|
+
state.sawResponseCompleted = true;
|
|
276
|
+
state.usage = data.response?.usage || null;
|
|
277
|
+
for (const item of data.response?.output || []) {
|
|
278
|
+
recordOutputItem(state, data.type, item);
|
|
279
|
+
await appendFinalImageFromItem(state, item, maxImages, requestId, onFinalImage);
|
|
280
|
+
}
|
|
281
|
+
const wsNum = data.response?.tool_usage?.web_search?.num_requests;
|
|
282
|
+
if (typeof wsNum === "number" && wsNum > state.webSearchCalls)
|
|
283
|
+
state.webSearchCalls = wsNum;
|
|
284
|
+
}
|
|
285
|
+
if (data.type === "error") {
|
|
286
|
+
throw makeStreamError("Responses stream returned an error", safeDiagnosticLabel(data.error?.code, "RESPONSES_STREAM_ERROR") || "RESPONSES_STREAM_ERROR", state.eventCount, eventType);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
state.finalBufferChars = buffer.length;
|
|
291
|
+
logEvent(scope, "stream_end", {
|
|
292
|
+
requestId,
|
|
293
|
+
events: state.eventCount,
|
|
294
|
+
imageCount: state.images.length,
|
|
295
|
+
webSearchCalls: state.webSearchCalls,
|
|
296
|
+
imageCallSeen: state.imageCallSeen,
|
|
297
|
+
messageOutputSeen: state.messageOutputSeen,
|
|
298
|
+
bytesRead: state.bytesRead,
|
|
299
|
+
parseSkipCount: state.parseSkipCount,
|
|
300
|
+
});
|
|
301
|
+
return resultFromState(state);
|
|
302
|
+
}
|
|
303
|
+
export async function parseJson(res, maxImages) {
|
|
304
|
+
const json = await res.json();
|
|
305
|
+
const state = createState();
|
|
306
|
+
state.usage = json.usage || null;
|
|
307
|
+
for (const item of json.output || []) {
|
|
308
|
+
state.eventCount++;
|
|
309
|
+
state.eventTypes["json.output"] = (state.eventTypes["json.output"] || 0) + 1;
|
|
310
|
+
recordOutputItem(state, "json.output", item);
|
|
311
|
+
if (item.type === "image_generation_call" && item.result && state.images.length < maxImages) {
|
|
312
|
+
state.images.push({
|
|
313
|
+
b64: item.result,
|
|
314
|
+
revisedPrompt: typeof item.revised_prompt === "string" ? item.revised_prompt : null,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
if (item.type === "web_search_call")
|
|
318
|
+
state.webSearchCalls++;
|
|
319
|
+
const itemText = extractJsonItemText(item);
|
|
320
|
+
if (itemText)
|
|
321
|
+
state.textOutput += `${state.textOutput ? "\n\n" : ""}${itemText}`;
|
|
322
|
+
}
|
|
323
|
+
return resultFromState(state);
|
|
324
|
+
}
|