ima2-gen 2.0.1 → 2.0.2
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 +150 -0
- package/README.md +10 -1
- package/bin/commands/backfillThumbs.js +6 -0
- package/bin/commands/gen.js +6 -0
- package/bin/ima2.js +14 -10
- package/docs/API.md +131 -8
- package/docs/CLI.md +2 -1
- package/docs/FAQ.ko.md +16 -0
- package/docs/FAQ.md +30 -0
- package/docs/README.ko.md +7 -3
- package/docs/migration/runtime-test-inventory.md +15 -1
- package/lib/agentImageVideoGen.js +261 -0
- package/lib/agentRuntime.js +7 -262
- package/lib/agyImageAdapter.js +35 -8
- package/lib/errorClassify.js +8 -7
- package/lib/eventBus.js +71 -0
- package/lib/geminiApiImageAdapter.js +16 -20
- package/lib/generationErrors.js +3 -1
- package/lib/grokImageAdapter.js +68 -129
- package/lib/grokImageCore.js +153 -0
- package/lib/grokMultimodeAdapter.js +5 -3
- package/lib/grokVideoCanvas.js +13 -0
- package/lib/grokVideoPlannerPrompt.js +53 -6
- package/lib/historyList.js +1 -0
- package/lib/inflight.js +54 -17
- package/lib/multimodeHelpers.js +10 -0
- package/lib/nodeHelpers.js +59 -0
- package/lib/oauthProxy/prompts.js +30 -36
- package/lib/promptBuilder/systemPrompt.js +2 -5
- package/lib/promptSafetyPolicy.js +1 -5
- package/lib/responsesFallback.js +2 -1
- package/lib/routeHelpers.js +44 -0
- package/lib/ssePublish.js +12 -0
- package/lib/storyboardPrefix.js +28 -0
- package/lib/thumbBackfill.js +16 -5
- package/package.json +4 -1
- package/routes/agy.js +44 -0
- package/routes/auth.js +6 -2
- package/routes/edit.js +7 -1
- package/routes/events.js +78 -0
- package/routes/generate.js +99 -127
- package/routes/index.js +4 -0
- package/routes/multimode.js +99 -56
- package/routes/nodes.js +59 -103
- package/routes/video.js +100 -17
- package/skills/ima2/SKILL.md +98 -21
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-CYv84Rus.js → AgentWorkspace-Dth6YijN.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-Dqyc1WZ1.js → CardNewsWorkspace-Dav3K5CT.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-ChEXzQbb.js → NodeCanvas-C4ifFzB1.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-B95ZufnR.js → PromptBuilderPanel-CEcyU9PL.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-DGOwFQET.js → PromptImportDialog-CgQ94Gth.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-CgvdnR49.js → PromptImportDiscoverySection-CuzyzbNI.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-CfUye9J8.js → PromptImportFolderSection-DHLGlO6l.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-B9kndPw1.js → PromptLibraryPanel-BOe18we8.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-Cdgnm4Wa.js +1 -0
- package/ui/dist/assets/{index-BhcvL0g-.js → index-C5PSahkr.js} +1 -1
- package/ui/dist/assets/index-Dn2AhL6d.css +1 -0
- package/ui/dist/assets/index-Tjqx6wUV.js +23 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/SettingsWorkspace-B3tgLrmF.js +0 -1
- package/ui/dist/assets/index-BtK3YhJc.js +0 -39
- package/ui/dist/assets/index-ClOLOjnA.css +0 -1
package/routes/generate.js
CHANGED
|
@@ -12,7 +12,7 @@ import { generateViaGrok, planGrokImage } from "../lib/grokImageAdapter.js";
|
|
|
12
12
|
import { generateViaAgy } from "../lib/agyImageAdapter.js";
|
|
13
13
|
import { generateViaGeminiApi } from "../lib/geminiApiImageAdapter.js";
|
|
14
14
|
import { isNonRetryableGenerationError, normalizeGenerationFailure } from "../lib/generationErrors.js";
|
|
15
|
-
import { startJob, finishJob, registerJobAbortController, isJobCanceled } from "../lib/inflight.js";
|
|
15
|
+
import { startJob, finishJob, registerJobAbortController, isJobCanceled, isStartJobFailure, setJobPhase, INFLIGHT_RETRY_AFTER_SECONDS, } from "../lib/inflight.js";
|
|
16
16
|
import { isGenerationCanceledError, makeGenerationCanceledError, throwIfJobCanceled, } from "../lib/generationCancel.js";
|
|
17
17
|
import { logEvent, logError } from "../lib/logger.js";
|
|
18
18
|
import { embedImageMetadataBestEffort } from "../lib/imageMetadataStore.js";
|
|
@@ -20,57 +20,44 @@ import { invalidateHistoryIndex } from "../lib/historyIndex.js";
|
|
|
20
20
|
import { normalizeComposerInsertedPrompts, normalizeComposerPrompt, } from "../lib/composerSnapshot.js";
|
|
21
21
|
import { errInfo } from "../lib/errInfo.js";
|
|
22
22
|
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return { moderation };
|
|
28
|
-
}
|
|
29
|
-
function imageFormatFromMime(mime) {
|
|
30
|
-
if (mime === "image/jpeg")
|
|
31
|
-
return "jpeg";
|
|
32
|
-
if (mime === "image/webp")
|
|
33
|
-
return "webp";
|
|
34
|
-
return "png";
|
|
35
|
-
}
|
|
23
|
+
import { STORYBOARD_PREFIX } from "../lib/storyboardPrefix.js";
|
|
24
|
+
import { validateModeration, imageFormatFromMime, upstreamErrorFields } from "../lib/routeHelpers.js";
|
|
25
|
+
import { publish } from "../lib/eventBus.js";
|
|
26
|
+
import { publishJobEvent } from "../lib/ssePublish.js";
|
|
36
27
|
export function registerGenerateRoutes(app, ctxRaw) {
|
|
37
28
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
38
29
|
app.post("/api/generate", async (req, res) => {
|
|
39
30
|
const requestId = typeof req.body?.requestId === "string" ? req.body.requestId : req.id;
|
|
31
|
+
const asyncMode = req.body?.async === true;
|
|
40
32
|
let finishStatus = "completed";
|
|
41
33
|
let finishHttpStatus;
|
|
42
34
|
let finishErrorCode;
|
|
43
35
|
let finishMeta = {};
|
|
44
36
|
let finishCanceled = false;
|
|
45
37
|
const cancelController = new AbortController();
|
|
38
|
+
const fail = (status, payload) => {
|
|
39
|
+
finishStatus = "error";
|
|
40
|
+
finishHttpStatus = status;
|
|
41
|
+
finishErrorCode = typeof payload.code === "string" ? payload.code : finishErrorCode;
|
|
42
|
+
if (asyncMode && res.headersSent) {
|
|
43
|
+
publish(requestId, "error", { ...payload, status, requestId });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
return res.status(status).json(payload);
|
|
47
|
+
};
|
|
48
|
+
const succeed = (payload) => {
|
|
49
|
+
if (asyncMode) {
|
|
50
|
+
publishJobEvent(requestId, "done", payload);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
res.json(payload);
|
|
54
|
+
};
|
|
46
55
|
try {
|
|
47
56
|
const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId : null;
|
|
48
57
|
const clientNodeId = typeof req.body?.clientNodeId === "string" ? req.body.clientNodeId : null;
|
|
49
58
|
const { prompt, quality: rawQuality = "medium", size = "1024x1024", format = "png", moderation = "low", provider = "auto", n = 1, references = [], mode: promptMode = "auto", model: rawModel, reasoningEffort: rawReasoningEffort, webSearchEnabled: rawWebSearchEnabled = true, } = req.body;
|
|
50
59
|
const storyboardActive = req.body?.storyboard === true;
|
|
51
|
-
const storyboardPrefix = storyboardActive
|
|
52
|
-
? [
|
|
53
|
-
"[STORYBOARD MODE — Video Production Keyframe]",
|
|
54
|
-
"This image is a keyframe for a multi-shot VIDEO storyboard. It will be animated via image-to-video.",
|
|
55
|
-
"The prompt and all injected instructions MUST be in English.",
|
|
56
|
-
"",
|
|
57
|
-
"CHARACTER LOCK:",
|
|
58
|
-
"- Identify each character by 2-3 VISUAL identifiers (clothing color + physique + position/props). Never by name alone.",
|
|
59
|
-
"- Copy character descriptions VERBATIM from the reference/prior frame. Do NOT rephrase or drift.",
|
|
60
|
-
"",
|
|
61
|
-
"SCENE CONTINUITY:",
|
|
62
|
-
"- Lock lighting direction, color palette, environment, and art style to prior frames.",
|
|
63
|
-
"- Change ONLY: action, shot scale, camera angle, or expression.",
|
|
64
|
-
"- Reference image = canonical anchor. Preserve it faithfully.",
|
|
65
|
-
"",
|
|
66
|
-
"VIDEO-READY COMPOSITION:",
|
|
67
|
-
"- Frame for animation: leave space for motion, avoid static-only poses.",
|
|
68
|
-
"- Use descriptive caption format: shot type + subject action + environment + technical (lens, lighting) + mood.",
|
|
69
|
-
"- Specify intended camera movement for the video phase (e.g. 'slow dolly-in', 'static wide').",
|
|
70
|
-
"- End pose must be stable and suitable for video continuation.",
|
|
71
|
-
"",
|
|
72
|
-
].join("\n") + "\n"
|
|
73
|
-
: "";
|
|
60
|
+
const storyboardPrefix = storyboardActive ? STORYBOARD_PREFIX : "";
|
|
74
61
|
const composerPrompt = normalizeComposerPrompt(req.body?.composerPrompt);
|
|
75
62
|
const composerInsertedPrompts = normalizeComposerInsertedPrompts(req.body?.composerInsertedPrompts);
|
|
76
63
|
const { quality, warnings: qualityWarnings } = normalizeOAuthParams({ provider, quality: rawQuality });
|
|
@@ -82,10 +69,7 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
82
69
|
rawWebSearchEnabled,
|
|
83
70
|
});
|
|
84
71
|
if (providerOptions.error) {
|
|
85
|
-
|
|
86
|
-
finishHttpStatus = providerOptions.status;
|
|
87
|
-
finishErrorCode = providerOptions.code;
|
|
88
|
-
return res.status(providerOptions.status).json({ error: providerOptions.error, code: providerOptions.code });
|
|
72
|
+
return fail(providerOptions.status, { error: providerOptions.error, code: providerOptions.code });
|
|
89
73
|
}
|
|
90
74
|
const imageModel = providerOptions.model;
|
|
91
75
|
const reasoningEffort = providerOptions.reasoningEffort;
|
|
@@ -95,13 +79,34 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
95
79
|
const normalizedPromptMode = promptMode === "direct" ? "direct" : "auto";
|
|
96
80
|
const generationPrompt = storyboardPrefix + prompt;
|
|
97
81
|
if (!prompt)
|
|
98
|
-
return
|
|
82
|
+
return fail(400, { error: "Prompt is required" });
|
|
99
83
|
const moderationCheck = validateModeration(ctx, moderation);
|
|
100
84
|
if (moderationCheck.error)
|
|
101
|
-
return
|
|
85
|
+
return fail(400, { error: moderationCheck.error });
|
|
102
86
|
const count = Math.min(Math.max(parseInt(n) || 1, 1), 8);
|
|
103
87
|
const referencePayload = summarizeReferencePayload(references);
|
|
104
|
-
|
|
88
|
+
const refCheckResult = validateAndNormalizeRefs(references);
|
|
89
|
+
if (refCheckResult.error) {
|
|
90
|
+
return fail(400, { error: refCheckResult.error, code: refCheckResult.code });
|
|
91
|
+
}
|
|
92
|
+
const refCheck = refCheckResult;
|
|
93
|
+
const incomingProviderUrl = typeof req.body?.providerUrl === "string" && req.body.providerUrl.startsWith("http")
|
|
94
|
+
? req.body.providerUrl
|
|
95
|
+
: null;
|
|
96
|
+
const grokRefs = incomingProviderUrl
|
|
97
|
+
? [{ b64: "", url: incomingProviderUrl, declaredMime: "image/png", detectedMime: "image/png" }, ...refCheck.refDetails]
|
|
98
|
+
: refCheck.refDetails;
|
|
99
|
+
const providerRefCount = activeProvider === "grok" || activeProvider === "grok-api"
|
|
100
|
+
? grokRefs.length
|
|
101
|
+
: refCheck.refs.length;
|
|
102
|
+
if ((activeProvider === "grok" || activeProvider === "agy" || activeProvider === "grok-api" || activeProvider === "gemini-api") && providerRefCount > 3) {
|
|
103
|
+
return fail(400, {
|
|
104
|
+
error: `${activeProvider === "agy" ? "Agy" : "Grok"} image editing supports up to 3 reference images`,
|
|
105
|
+
code: activeProvider === "agy" ? "AGY_REF_TOO_MANY" : "GROK_REF_TOO_MANY",
|
|
106
|
+
requestId,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const started = startJob({
|
|
105
110
|
requestId,
|
|
106
111
|
kind: "classic",
|
|
107
112
|
prompt,
|
|
@@ -114,32 +119,33 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
114
119
|
model: imageModel,
|
|
115
120
|
size: effectiveSize,
|
|
116
121
|
n: count,
|
|
117
|
-
refsCount:
|
|
122
|
+
refsCount: providerRefCount,
|
|
118
123
|
referenceBytes: referencePayload.referenceBytes,
|
|
119
124
|
referenceB64Chars: referencePayload.referenceB64Chars,
|
|
120
125
|
composerPrompt,
|
|
121
126
|
composerInsertedPrompts,
|
|
122
127
|
},
|
|
123
128
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
finishStatus = "error";
|
|
135
|
-
finishHttpStatus = 400;
|
|
136
|
-
finishErrorCode = activeProvider === "agy" ? "AGY_REF_TOO_MANY" : "GROK_REF_TOO_MANY";
|
|
137
|
-
return res.status(400).json({
|
|
138
|
-
error: `${activeProvider === "agy" ? "Agy" : "Grok"} image editing supports up to 3 reference images`,
|
|
139
|
-
code: activeProvider === "agy" ? "AGY_REF_TOO_MANY" : "GROK_REF_TOO_MANY",
|
|
129
|
+
if (started && isStartJobFailure(started)) {
|
|
130
|
+
const status = started.code === "TOO_MANY_JOBS" ? 429 : 409;
|
|
131
|
+
if (started.code === "TOO_MANY_JOBS") {
|
|
132
|
+
res.setHeader("Retry-After", String(INFLIGHT_RETRY_AFTER_SECONDS));
|
|
133
|
+
}
|
|
134
|
+
return fail(status, {
|
|
135
|
+
error: started.code === "TOO_MANY_JOBS"
|
|
136
|
+
? "Too many concurrent generation jobs"
|
|
137
|
+
: "Request ID already in use",
|
|
138
|
+
code: started.code,
|
|
140
139
|
requestId,
|
|
141
140
|
});
|
|
142
141
|
}
|
|
142
|
+
registerJobAbortController(requestId, cancelController);
|
|
143
|
+
if (asyncMode) {
|
|
144
|
+
res.status(202).json({ requestId, async: true });
|
|
145
|
+
}
|
|
146
|
+
setJobPhase(requestId, "streaming");
|
|
147
|
+
if (asyncMode)
|
|
148
|
+
publish(requestId, "phase", { requestId, phase: "streaming" });
|
|
143
149
|
const client = req.get("x-ima2-client") || "ui";
|
|
144
150
|
const referenceDiagnostics = refCheck.referenceDiagnostics || [];
|
|
145
151
|
const referenceMismatchCount = referenceDiagnostics.filter((ref) => ref.warnings?.includes("mime_mismatch")).length;
|
|
@@ -152,7 +158,7 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
152
158
|
size: effectiveSize,
|
|
153
159
|
moderation,
|
|
154
160
|
n: count,
|
|
155
|
-
refs:
|
|
161
|
+
refs: providerRefCount,
|
|
156
162
|
referenceBytes: referencePayload.referenceBytes,
|
|
157
163
|
referenceMismatchCount,
|
|
158
164
|
refDetectedMimes: [...new Set(referenceDiagnostics.map((ref) => ref.detectedMime).filter(Boolean))].join(","),
|
|
@@ -175,8 +181,8 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
175
181
|
size: effectiveSize,
|
|
176
182
|
signal: cancelController.signal,
|
|
177
183
|
requestId,
|
|
178
|
-
referenceCount:
|
|
179
|
-
references:
|
|
184
|
+
referenceCount: grokRefs.length,
|
|
185
|
+
references: grokRefs,
|
|
180
186
|
directApiKey: grokDirectApiKey,
|
|
181
187
|
})
|
|
182
188
|
: null;
|
|
@@ -210,7 +216,7 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
210
216
|
requestId,
|
|
211
217
|
plannedPrompt: sharedGrokPlan?.prompt,
|
|
212
218
|
webSearchCalls: sharedGrokPlan?.webSearchCalls,
|
|
213
|
-
references:
|
|
219
|
+
references: grokRefs,
|
|
214
220
|
directApiKey: grokDirectApiKey,
|
|
215
221
|
});
|
|
216
222
|
throwIfJobCanceled(requestId);
|
|
@@ -275,6 +281,11 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
275
281
|
}
|
|
276
282
|
const rand = randomBytes(ctx.config.ids.generatedHexBytes).toString("hex");
|
|
277
283
|
const filename = `${Date.now()}_${rand}_${images.length}.${resultFormat}`;
|
|
284
|
+
const createdAt = Date.now();
|
|
285
|
+
const valueWithProviderUrl = r.value;
|
|
286
|
+
const providerUrl = typeof valueWithProviderUrl.providerUrl === "string"
|
|
287
|
+
? valueWithProviderUrl.providerUrl
|
|
288
|
+
: undefined;
|
|
278
289
|
const meta = {
|
|
279
290
|
kind: "classic",
|
|
280
291
|
requestId,
|
|
@@ -293,11 +304,12 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
293
304
|
model: activeProvider === "grok" ? (quality === "high" ? "grok-imagine-image-quality" : imageModel) : imageModel,
|
|
294
305
|
reasoningEffort,
|
|
295
306
|
provider: activeProvider,
|
|
296
|
-
createdAt
|
|
307
|
+
createdAt,
|
|
308
|
+
...(providerUrl ? { providerUrl } : {}),
|
|
297
309
|
usage: r.value.usage || null,
|
|
298
310
|
webSearchCalls: r.value.webSearchCalls || 0,
|
|
299
311
|
webSearchEnabled,
|
|
300
|
-
refsCount:
|
|
312
|
+
refsCount: providerRefCount,
|
|
301
313
|
};
|
|
302
314
|
const rawBuffer = Buffer.from(r.value.b64, "base64");
|
|
303
315
|
const embedded = await embedImageMetadataBestEffort(rawBuffer, resultFormat, meta, {
|
|
@@ -320,6 +332,8 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
320
332
|
image: `data:${resultMime};base64,${r.value.b64}`,
|
|
321
333
|
filename,
|
|
322
334
|
revisedPrompt: r.value.revisedPrompt || null,
|
|
335
|
+
...(providerUrl ? { providerUrl } : {}),
|
|
336
|
+
createdAt,
|
|
323
337
|
});
|
|
324
338
|
if (r.value.usage) {
|
|
325
339
|
const usageValue = r.value.usage;
|
|
@@ -349,47 +363,20 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
349
363
|
const status = firstErr.status || 500;
|
|
350
364
|
if (isGenerationCanceledError(firstErr)) {
|
|
351
365
|
finishCanceled = true;
|
|
352
|
-
|
|
353
|
-
finishErrorCode = firstErr.code;
|
|
354
|
-
return res.status(firstErr.status).json({
|
|
366
|
+
return fail(firstErr.status, {
|
|
355
367
|
error: firstErr.message,
|
|
356
368
|
code: firstErr.code,
|
|
357
369
|
requestId,
|
|
358
370
|
});
|
|
359
371
|
}
|
|
360
|
-
|
|
361
|
-
finishHttpStatus = status;
|
|
362
|
-
finishErrorCode = firstErr.code;
|
|
363
|
-
return res.status(status).json({
|
|
372
|
+
return fail(status, {
|
|
364
373
|
error: firstErr.message,
|
|
365
374
|
code: firstErr.code,
|
|
366
|
-
|
|
367
|
-
upstreamType: firstErr.upstreamType || null,
|
|
368
|
-
upstreamParam: firstErr.upstreamParam || null,
|
|
369
|
-
diagnosticReason: firstErr.diagnosticReason || null,
|
|
370
|
-
retryKind: firstErr.retryKind || null,
|
|
371
|
-
initialEventCount: firstErr.initialEventCount ?? null,
|
|
372
|
-
initialEventTypes: firstErr.initialEventTypes || null,
|
|
373
|
-
referencesDroppedOnRetry: firstErr.referencesDroppedOnRetry ?? null,
|
|
374
|
-
developerPromptDroppedOnRetry: firstErr.developerPromptDroppedOnRetry ?? null,
|
|
375
|
-
webSearchDroppedOnRetry: firstErr.webSearchDroppedOnRetry ?? null,
|
|
376
|
-
fallbackEventCount: firstErr.fallbackEventCount ?? null,
|
|
377
|
-
fallbackEventTypes: firstErr.fallbackEventTypes || null,
|
|
378
|
-
fallbackImageCallSeen: firstErr.fallbackImageCallSeen ?? null,
|
|
379
|
-
fallbackImageResultCount: firstErr.fallbackImageResultCount ?? null,
|
|
380
|
-
errorEventCount: firstErr.eventCount ?? null,
|
|
381
|
-
eventTypes: firstErr.eventTypes || null,
|
|
382
|
-
webSearchCalls: firstErr.webSearchCalls ?? null,
|
|
383
|
-
responseDiagnostics: firstErr.responseDiagnostics || null,
|
|
384
|
-
toolTypes: firstErr.toolTypes || null,
|
|
385
|
-
toolChoiceKind: firstErr.toolChoiceKind || null,
|
|
375
|
+
...upstreamErrorFields(firstErr),
|
|
386
376
|
requestId,
|
|
387
377
|
});
|
|
388
378
|
}
|
|
389
|
-
|
|
390
|
-
finishHttpStatus = 500;
|
|
391
|
-
finishErrorCode = "GENERATE_ALL_FAILED";
|
|
392
|
-
return res.status(500).json({ error: "All generation attempts failed" });
|
|
379
|
+
return fail(500, { error: "All generation attempts failed", code: "GENERATE_ALL_FAILED", requestId });
|
|
393
380
|
}
|
|
394
381
|
const elapsed = +((Date.now() - startTime) / 1000).toFixed(1);
|
|
395
382
|
// Persist elapsed (computed after the generation loop) into each image's sidecar.
|
|
@@ -431,7 +418,15 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
431
418
|
elapsedMs: Date.now() - startTime,
|
|
432
419
|
filename: images[0].filename,
|
|
433
420
|
});
|
|
434
|
-
|
|
421
|
+
succeed({
|
|
422
|
+
image: images[0].image,
|
|
423
|
+
elapsed,
|
|
424
|
+
filename: images[0].filename,
|
|
425
|
+
requestId,
|
|
426
|
+
providerUrl: images[0].providerUrl ?? null,
|
|
427
|
+
createdAt: images[0].createdAt,
|
|
428
|
+
...extra,
|
|
429
|
+
});
|
|
435
430
|
}
|
|
436
431
|
else {
|
|
437
432
|
finishHttpStatus = 200;
|
|
@@ -441,7 +436,7 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
441
436
|
imageCount: images.length,
|
|
442
437
|
elapsedMs: Date.now() - startTime,
|
|
443
438
|
});
|
|
444
|
-
|
|
439
|
+
succeed({ images, elapsed, count: images.length, requestId, ...extra });
|
|
445
440
|
}
|
|
446
441
|
}
|
|
447
442
|
catch (e) {
|
|
@@ -451,41 +446,18 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
451
446
|
if (isGenerationCanceledError(err.raw) || isJobCanceled(requestId)) {
|
|
452
447
|
const canceled = makeGenerationCanceledError();
|
|
453
448
|
finishCanceled = true;
|
|
454
|
-
|
|
455
|
-
finishErrorCode = canceled.code;
|
|
456
|
-
return res.status(canceled.status).json({
|
|
449
|
+
return fail(canceled.status, {
|
|
457
450
|
error: canceled.message,
|
|
458
451
|
code: canceled.code,
|
|
459
452
|
requestId,
|
|
460
453
|
});
|
|
461
454
|
}
|
|
462
|
-
finishStatus = "error";
|
|
463
|
-
finishHttpStatus = err.status || 500;
|
|
464
455
|
finishErrorCode = fallbackCode || "GENERATE_FAILED";
|
|
465
456
|
logError("generate", "error", err.raw, { requestId, code: finishErrorCode });
|
|
466
|
-
|
|
457
|
+
fail(err.status || 500, {
|
|
467
458
|
error: err.message,
|
|
468
|
-
code:
|
|
469
|
-
|
|
470
|
-
upstreamType: ext.upstreamType || null,
|
|
471
|
-
upstreamParam: ext.upstreamParam || null,
|
|
472
|
-
diagnosticReason: ext.diagnosticReason || null,
|
|
473
|
-
retryKind: ext.retryKind || null,
|
|
474
|
-
initialEventCount: ext.initialEventCount ?? null,
|
|
475
|
-
initialEventTypes: ext.initialEventTypes || null,
|
|
476
|
-
referencesDroppedOnRetry: ext.referencesDroppedOnRetry ?? null,
|
|
477
|
-
developerPromptDroppedOnRetry: ext.developerPromptDroppedOnRetry ?? null,
|
|
478
|
-
webSearchDroppedOnRetry: ext.webSearchDroppedOnRetry ?? null,
|
|
479
|
-
fallbackEventCount: ext.fallbackEventCount ?? null,
|
|
480
|
-
fallbackEventTypes: ext.fallbackEventTypes || null,
|
|
481
|
-
fallbackImageCallSeen: ext.fallbackImageCallSeen ?? null,
|
|
482
|
-
fallbackImageResultCount: ext.fallbackImageResultCount ?? null,
|
|
483
|
-
errorEventCount: ext.eventCount ?? null,
|
|
484
|
-
eventTypes: ext.eventTypes || null,
|
|
485
|
-
webSearchCalls: ext.webSearchCalls ?? null,
|
|
486
|
-
responseDiagnostics: ext.responseDiagnostics || null,
|
|
487
|
-
toolTypes: ext.toolTypes || null,
|
|
488
|
-
toolChoiceKind: ext.toolChoiceKind || null,
|
|
459
|
+
code: finishErrorCode,
|
|
460
|
+
...upstreamErrorFields(ext),
|
|
489
461
|
requestId,
|
|
490
462
|
});
|
|
491
463
|
}
|
package/routes/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { registerCapabilitiesRoutes } from "./capabilities.js";
|
|
2
|
+
import { registerEventsRoute } from "./events.js";
|
|
2
3
|
import { registerHealthRoutes } from "./health.js";
|
|
3
4
|
import { registerHistoryRoutes } from "./history.js";
|
|
4
5
|
import { registerSessionRoutes } from "./sessions.js";
|
|
@@ -18,6 +19,7 @@ import { registerImageImportRoutes } from "./imageImport.js";
|
|
|
18
19
|
import { registerPromptBuilderRoutes } from "./promptBuilder.js";
|
|
19
20
|
import { registerAgentRoutes } from "./agent.js";
|
|
20
21
|
import { registerGrokRoutes } from "./grok.js";
|
|
22
|
+
import { registerAgyRoutes } from "./agy.js";
|
|
21
23
|
import { registerVideoRoutes } from "./video.js";
|
|
22
24
|
import { registerVideoExtendedRoutes } from "./videoExtended.js";
|
|
23
25
|
import { registerQuotaRoutes } from "./quota.js";
|
|
@@ -26,6 +28,7 @@ import { mountKeyRoutes } from "./keys.js";
|
|
|
26
28
|
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
27
29
|
export function configureRoutes(app, ctxRaw) {
|
|
28
30
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
31
|
+
registerEventsRoute(app, ctx);
|
|
29
32
|
registerHealthRoutes(app, ctx);
|
|
30
33
|
registerCapabilitiesRoutes(app, ctx);
|
|
31
34
|
registerStorageRoutes(app, ctx);
|
|
@@ -47,6 +50,7 @@ export function configureRoutes(app, ctxRaw) {
|
|
|
47
50
|
registerPromptRoutes(app, ctx);
|
|
48
51
|
registerPromptImportRoutes(app, ctx);
|
|
49
52
|
registerGrokRoutes(app, ctx);
|
|
53
|
+
registerAgyRoutes(app);
|
|
50
54
|
registerVideoRoutes(app, ctx);
|
|
51
55
|
registerVideoExtendedRoutes(app, ctx);
|
|
52
56
|
registerQuotaRoutes(app, ctx);
|