ima2-gen 1.1.21 → 1.1.23

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.
Files changed (214) hide show
  1. package/README.md +44 -7
  2. package/bin/commands/video.js +14 -0
  3. package/bin/ima2.js +14 -4
  4. package/bin/lib/platform.js +34 -5
  5. package/docs/README.ko.md +43 -2
  6. package/lib/agentQueueWorker.js +6 -0
  7. package/lib/agentRuntime.js +3 -2
  8. package/lib/atomicWrite.js +14 -0
  9. package/lib/grokImageAdapter.js +6 -0
  10. package/lib/grokProxyLauncher.js +5 -3
  11. package/lib/grokVideoAdapter.js +1 -1
  12. package/lib/grokVideoPlannerPrompt.js +10 -0
  13. package/lib/inflight.js +1 -1
  14. package/lib/oauthLauncher.js +5 -0
  15. package/lib/videoFrameExtract.js +3 -3
  16. package/package.json +5 -7
  17. package/routes/capabilities.js +13 -0
  18. package/routes/edit.js +2 -1
  19. package/routes/generate.js +32 -6
  20. package/routes/health.js +4 -3
  21. package/routes/multimode.js +2 -1
  22. package/routes/video.js +35 -3
  23. package/server.js +29 -2
  24. package/skills/ima2/SKILL.md +48 -6
  25. package/ui/dist/.vite/manifest.json +12 -12
  26. package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-C21zqdTZ.js} +1 -1
  27. package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-BN-ga1lG.js} +1 -1
  28. package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BbMa4IhI.js} +1 -1
  29. package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DRwBJRDQ.js} +1 -1
  30. package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-Dp85kHCq.js} +2 -2
  31. package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BE8Q8MLD.js} +1 -1
  32. package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-PtH5x0sc.js} +1 -1
  33. package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-FnM9tHI9.js} +2 -2
  34. package/ui/dist/assets/SettingsWorkspace-MARPGyBL.js +1 -0
  35. package/ui/dist/assets/index-BAFI6htx.js +42 -0
  36. package/ui/dist/assets/{index-31uVIdt4.js → index-BSXxr_Bt.js} +1 -1
  37. package/ui/dist/assets/index-DS-ADE7U.css +1 -0
  38. package/ui/dist/index.html +2 -2
  39. package/bin/commands/annotate.ts +0 -119
  40. package/bin/commands/cancel.ts +0 -48
  41. package/bin/commands/canvas-versions.ts +0 -80
  42. package/bin/commands/capabilities.ts +0 -110
  43. package/bin/commands/cardnews.ts +0 -249
  44. package/bin/commands/comfy.ts +0 -54
  45. package/bin/commands/config.ts +0 -186
  46. package/bin/commands/defaults.ts +0 -192
  47. package/bin/commands/doctor.ts +0 -202
  48. package/bin/commands/edit.ts +0 -150
  49. package/bin/commands/gen.ts +0 -214
  50. package/bin/commands/grok.ts +0 -90
  51. package/bin/commands/history.ts +0 -146
  52. package/bin/commands/ls.ts +0 -64
  53. package/bin/commands/metadata.ts +0 -39
  54. package/bin/commands/multimode.ts +0 -196
  55. package/bin/commands/node.ts +0 -166
  56. package/bin/commands/observability.ts +0 -176
  57. package/bin/commands/ping.ts +0 -31
  58. package/bin/commands/prompt-sub/build.ts +0 -101
  59. package/bin/commands/prompt.ts +0 -492
  60. package/bin/commands/ps.ts +0 -81
  61. package/bin/commands/session.ts +0 -266
  62. package/bin/commands/show.ts +0 -72
  63. package/bin/commands/skill.ts +0 -70
  64. package/bin/commands/video.ts +0 -442
  65. package/bin/ima2.ts +0 -430
  66. package/bin/lib/args.ts +0 -92
  67. package/bin/lib/browser-id.ts +0 -16
  68. package/bin/lib/client.ts +0 -122
  69. package/bin/lib/config-store.ts +0 -120
  70. package/bin/lib/destructive-confirm.ts +0 -19
  71. package/bin/lib/doctor-checks.ts +0 -91
  72. package/bin/lib/error-hints.ts +0 -23
  73. package/bin/lib/files.ts +0 -39
  74. package/bin/lib/output.ts +0 -73
  75. package/bin/lib/platform.ts +0 -99
  76. package/bin/lib/recover-output.ts +0 -139
  77. package/bin/lib/sse.ts +0 -73
  78. package/bin/lib/star-prompt.ts +0 -97
  79. package/bin/lib/storage-doctor.ts +0 -39
  80. package/bin/lib/ui-build.ts +0 -85
  81. package/config.ts +0 -354
  82. package/lib/agentCommandParser.ts +0 -69
  83. package/lib/agentGenerationPlanner.ts +0 -273
  84. package/lib/agentQuestionResponder.ts +0 -266
  85. package/lib/agentQueueStore.ts +0 -270
  86. package/lib/agentQueueWorker.ts +0 -89
  87. package/lib/agentRuntime.ts +0 -604
  88. package/lib/agentSettings.ts +0 -72
  89. package/lib/agentStore.ts +0 -422
  90. package/lib/agentStoreRows.ts +0 -136
  91. package/lib/agentTypes.ts +0 -154
  92. package/lib/apiCachePolicy.ts +0 -11
  93. package/lib/assetLifecycle.ts +0 -146
  94. package/lib/canvasVersionStore.ts +0 -223
  95. package/lib/capabilities.ts +0 -126
  96. package/lib/cardNewsGenerator.ts +0 -271
  97. package/lib/cardNewsJobStore.ts +0 -142
  98. package/lib/cardNewsManifestStore.ts +0 -154
  99. package/lib/cardNewsPlanner.ts +0 -236
  100. package/lib/cardNewsPlannerClient.ts +0 -155
  101. package/lib/cardNewsPlannerPrompt.ts +0 -62
  102. package/lib/cardNewsPlannerSchema.ts +0 -321
  103. package/lib/cardNewsRoleTemplateStore.ts +0 -47
  104. package/lib/cardNewsTemplateStore.ts +0 -252
  105. package/lib/codexDetect.ts +0 -71
  106. package/lib/comfyBridge.ts +0 -235
  107. package/lib/composerSnapshot.ts +0 -33
  108. package/lib/configKeys.ts +0 -62
  109. package/lib/db.ts +0 -295
  110. package/lib/errInfo.ts +0 -43
  111. package/lib/errorClassify.ts +0 -100
  112. package/lib/generationCancel.ts +0 -28
  113. package/lib/generationErrors.ts +0 -238
  114. package/lib/grokImageAdapter.ts +0 -513
  115. package/lib/grokMultimodeAdapter.ts +0 -84
  116. package/lib/grokProxyLauncher.ts +0 -153
  117. package/lib/grokRuntime.ts +0 -23
  118. package/lib/grokSizeMapper.ts +0 -71
  119. package/lib/grokVideoAdapter.ts +0 -458
  120. package/lib/grokVideoCanvas.ts +0 -26
  121. package/lib/grokVideoDownload.ts +0 -59
  122. package/lib/grokVideoPlannerPrompt.ts +0 -67
  123. package/lib/historyIndex.ts +0 -51
  124. package/lib/historyList.ts +0 -181
  125. package/lib/imageMetadata.ts +0 -113
  126. package/lib/imageMetadataStore.ts +0 -67
  127. package/lib/imageModels.ts +0 -165
  128. package/lib/inflight.ts +0 -281
  129. package/lib/localImportStore.ts +0 -114
  130. package/lib/logger.ts +0 -161
  131. package/lib/nodeStore.ts +0 -91
  132. package/lib/oauthLauncher.ts +0 -94
  133. package/lib/oauthNormalize.ts +0 -30
  134. package/lib/oauthProxy/errors.ts +0 -128
  135. package/lib/oauthProxy/generators.ts +0 -494
  136. package/lib/oauthProxy/index.ts +0 -28
  137. package/lib/oauthProxy/prompts.ts +0 -123
  138. package/lib/oauthProxy/references.ts +0 -45
  139. package/lib/oauthProxy/runtime.ts +0 -115
  140. package/lib/oauthProxy/streams.ts +0 -232
  141. package/lib/oauthProxy/types.ts +0 -9
  142. package/lib/oauthProxy.ts +0 -3
  143. package/lib/openDirectory.ts +0 -47
  144. package/lib/pngInfo.ts +0 -26
  145. package/lib/promptBuilder/attachments.ts +0 -74
  146. package/lib/promptBuilder/client.ts +0 -130
  147. package/lib/promptBuilder/constants.ts +0 -9
  148. package/lib/promptBuilder/context.ts +0 -36
  149. package/lib/promptBuilder/errors.ts +0 -12
  150. package/lib/promptBuilder/requestSchema.ts +0 -56
  151. package/lib/promptBuilder/responseParser.ts +0 -219
  152. package/lib/promptBuilder/systemPrompt.ts +0 -135
  153. package/lib/promptBuilder/transport.ts +0 -94
  154. package/lib/promptBuilder/types.ts +0 -109
  155. package/lib/promptImport/curatedSources.ts +0 -141
  156. package/lib/promptImport/discoveryRegistry.ts +0 -329
  157. package/lib/promptImport/errors.ts +0 -18
  158. package/lib/promptImport/githubDiscovery.ts +0 -309
  159. package/lib/promptImport/githubFolder.ts +0 -397
  160. package/lib/promptImport/githubSource.ts +0 -257
  161. package/lib/promptImport/gptImageHints.ts +0 -70
  162. package/lib/promptImport/parsePromptCandidates.ts +0 -179
  163. package/lib/promptImport/promptIndex.ts +0 -326
  164. package/lib/promptImport/rankPromptCandidates.ts +0 -65
  165. package/lib/promptImport/types.ts +0 -103
  166. package/lib/promptSafetyPolicy.ts +0 -5
  167. package/lib/providerOptions.ts +0 -56
  168. package/lib/referenceImageCompress.ts +0 -84
  169. package/lib/refs.ts +0 -133
  170. package/lib/requestLogger.ts +0 -49
  171. package/lib/responsesDoctor.ts +0 -456
  172. package/lib/responsesErrors.ts +0 -83
  173. package/lib/responsesFallback.ts +0 -114
  174. package/lib/responsesImageAdapter.ts +0 -466
  175. package/lib/responsesParse.ts +0 -452
  176. package/lib/responsesTools.ts +0 -28
  177. package/lib/runtimeContext.ts +0 -146
  178. package/lib/runtimePorts.ts +0 -105
  179. package/lib/sessionStore.ts +0 -308
  180. package/lib/storageMigration.ts +0 -310
  181. package/lib/styleSheet.ts +0 -139
  182. package/lib/systemTrash.ts +0 -20
  183. package/lib/videoContinuity.ts +0 -180
  184. package/lib/videoFrameExtract.ts +0 -78
  185. package/lib/videoSeriesChain.ts +0 -29
  186. package/lib/visibleTextLanguagePolicy.ts +0 -7
  187. package/routes/agent.ts +0 -308
  188. package/routes/annotations.ts +0 -118
  189. package/routes/canvasVersions.ts +0 -69
  190. package/routes/capabilities.ts +0 -18
  191. package/routes/cardNews.ts +0 -211
  192. package/routes/comfy.ts +0 -43
  193. package/routes/edit.ts +0 -352
  194. package/routes/generate.ts +0 -492
  195. package/routes/grok.ts +0 -24
  196. package/routes/health.ts +0 -123
  197. package/routes/history.ts +0 -221
  198. package/routes/imageImport.ts +0 -37
  199. package/routes/index.ts +0 -52
  200. package/routes/metadata.ts +0 -77
  201. package/routes/multimode.ts +0 -499
  202. package/routes/nodes.ts +0 -578
  203. package/routes/promptBuilder.ts +0 -37
  204. package/routes/promptImport.ts +0 -379
  205. package/routes/prompts.ts +0 -428
  206. package/routes/quota.ts +0 -89
  207. package/routes/sessions.ts +0 -317
  208. package/routes/storage.ts +0 -47
  209. package/routes/video.ts +0 -300
  210. package/routes/videoExtended.ts +0 -284
  211. package/server.ts +0 -293
  212. package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
  213. package/ui/dist/assets/index-CjgnNtgt.css +0 -1
  214. package/ui/dist/assets/index-Da2s4_-5.js +0 -36
@@ -1,317 +0,0 @@
1
- import type { Express, Request, Response } from "express";
2
- import {
3
- createSession,
4
- listSessions,
5
- getSession,
6
- renameSession,
7
- deleteSession,
8
- saveGraph,
9
- getStyleSheet,
10
- setStyleSheet,
11
- setStyleSheetEnabled,
12
- } from "../lib/sessionStore.js";
13
- import { extractStyleSheet } from "../lib/styleSheet.js";
14
- import { logError, logEvent } from "../lib/logger.js";
15
-
16
- import { errInfo } from "../lib/errInfo.js";
17
- import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
18
- function safeJsonChars(value: unknown): number {
19
- try {
20
- return JSON.stringify(value ?? null).length;
21
- } catch {
22
- return 0;
23
- }
24
- }
25
-
26
- type IdParams = { id: string };
27
-
28
- export function registerSessionRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
29
- const ctx = requireRuntimeContext(ctxRaw);
30
- app.get("/api/sessions", (_req: Request, res: Response) => {
31
- try {
32
- res.json({ sessions: listSessions() });
33
- } catch (e) {
34
- const err = errInfo(e);
35
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
36
- }
37
- });
38
-
39
- app.post("/api/sessions", (req: Request, res: Response) => {
40
- try {
41
- const body = (req.body ?? {}) as { title?: unknown };
42
- const titleRaw = typeof body.title === "string" ? body.title : "Untitled";
43
- const title = (titleRaw || "Untitled").slice(0, 200);
44
- const session = createSession({ title });
45
- logEvent("session", "create", {
46
- sessionId: session.id,
47
- titleChars: session.title.length,
48
- });
49
- res.status(201).json({ session });
50
- } catch (e) {
51
- const err = errInfo(e);
52
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
53
- }
54
- });
55
-
56
- app.get("/api/sessions/:id", (req: Request<IdParams>, res: Response) => {
57
- try {
58
- const session = getSession(req.params.id);
59
- if (!session) {
60
- return res.status(404).json({
61
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
62
- });
63
- }
64
- res.json({ session });
65
- } catch (e) {
66
- const err = errInfo(e);
67
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
68
- }
69
- });
70
-
71
- app.patch("/api/sessions/:id", (req: Request<IdParams>, res: Response) => {
72
- try {
73
- const body = (req.body ?? {}) as { title?: unknown };
74
- const title = body.title;
75
- if (typeof title !== "string" || !title.trim()) {
76
- return res.status(400).json({
77
- error: { code: "INVALID_TITLE", message: "Title required" },
78
- });
79
- }
80
- const ok = renameSession(req.params.id, title.slice(0, 200));
81
- if (!ok) {
82
- return res.status(404).json({
83
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
84
- });
85
- }
86
- logEvent("session", "rename", {
87
- sessionId: req.params.id,
88
- titleChars: title.slice(0, 200).length,
89
- });
90
- res.json({ ok: true });
91
- } catch (e) {
92
- const err = errInfo(e);
93
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
94
- }
95
- });
96
-
97
- app.delete("/api/sessions/:id", (req: Request<IdParams>, res: Response) => {
98
- try {
99
- const ok = deleteSession(req.params.id);
100
- if (!ok) {
101
- return res.status(404).json({
102
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
103
- });
104
- }
105
- logEvent("session", "delete", { sessionId: req.params.id });
106
- res.json({ ok: true });
107
- } catch (e) {
108
- const err = errInfo(e);
109
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
110
- }
111
- });
112
-
113
- app.get("/api/sessions/:id/style-sheet", (req: Request<IdParams>, res: Response) => {
114
- try {
115
- const data = getStyleSheet(req.params.id);
116
- if (!data) {
117
- return res.status(404).json({
118
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
119
- });
120
- }
121
- logEvent("session", "stylesheet_get", {
122
- sessionId: req.params.id,
123
- enabled: data.enabled,
124
- hasSheet: !!data.styleSheet,
125
- sheetChars: safeJsonChars(data.styleSheet),
126
- });
127
- res.json(data);
128
- } catch (e) {
129
- const err = errInfo(e);
130
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
131
- }
132
- });
133
-
134
- app.put("/api/sessions/:id/style-sheet", (req: Request<IdParams>, res: Response) => {
135
- try {
136
- const body = (req.body ?? {}) as { styleSheet?: unknown; enabled?: unknown };
137
- const { styleSheet, enabled } = body;
138
- if (styleSheet !== null && (typeof styleSheet !== "object" || Array.isArray(styleSheet))) {
139
- return res.status(400).json({
140
- error: { code: "INVALID_SHEET", message: "styleSheet must be an object or null" },
141
- });
142
- }
143
- if (enabled !== undefined && typeof enabled !== "boolean") {
144
- return res.status(400).json({
145
- error: { code: "INVALID_ENABLED", message: "enabled must be boolean when provided" },
146
- });
147
- }
148
- const ok = setStyleSheet(req.params.id, styleSheet);
149
- if (!ok) {
150
- return res.status(404).json({
151
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
152
- });
153
- }
154
- if (typeof enabled === "boolean") setStyleSheetEnabled(req.params.id, enabled);
155
- logEvent("session", "stylesheet_save", {
156
- sessionId: req.params.id,
157
- enabled: typeof enabled === "boolean" ? enabled : undefined,
158
- hasSheet: !!styleSheet,
159
- sheetChars: safeJsonChars(styleSheet),
160
- });
161
- res.json({ ok: true });
162
- } catch (e) {
163
- const err = errInfo(e);
164
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
165
- }
166
- });
167
-
168
- app.patch("/api/sessions/:id/style-sheet/enabled", (req: Request<IdParams>, res: Response) => {
169
- try {
170
- const body = (req.body ?? {}) as { enabled?: unknown };
171
- const { enabled } = body;
172
- if (typeof enabled !== "boolean") {
173
- return res.status(400).json({
174
- error: { code: "INVALID_ENABLED", message: "enabled must be boolean" },
175
- });
176
- }
177
- const ok = setStyleSheetEnabled(req.params.id, enabled);
178
- if (!ok) {
179
- return res.status(404).json({
180
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
181
- });
182
- }
183
- logEvent("session", "stylesheet_toggle", {
184
- sessionId: req.params.id,
185
- enabled,
186
- });
187
- res.json({ ok: true, enabled });
188
- } catch (e) {
189
- const err = errInfo(e);
190
- res.status(500).json({ error: { code: "DB_ERROR", message: err.message } });
191
- }
192
- });
193
-
194
- app.post("/api/sessions/:id/style-sheet/extract", async (req: Request<IdParams>, res: Response) => {
195
- try {
196
- if (!ctx.openai) {
197
- return res.status(400).json({
198
- error: {
199
- code: "STYLE_SHEET_NO_KEY",
200
- message: "Style-sheet extraction requires an OpenAI API key. Connect one via setup.",
201
- },
202
- });
203
- }
204
- const body = (req.body ?? {}) as { prompt?: unknown; referenceDataUrl?: unknown };
205
- const { prompt, referenceDataUrl } = body;
206
- if (typeof prompt !== "string" || !prompt.trim()) {
207
- return res.status(400).json({
208
- error: { code: "STYLE_SHEET_BAD_INPUT", message: "prompt required" },
209
- });
210
- }
211
- if (!getSession(req.params.id)) {
212
- return res.status(404).json({
213
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
214
- });
215
- }
216
- logEvent("session", "stylesheet_extract_start", {
217
- sessionId: req.params.id,
218
- promptChars: prompt.length,
219
- hasReference: typeof referenceDataUrl === "string" && referenceDataUrl.length > 0,
220
- });
221
- const sheet = await extractStyleSheet(ctx.openai, {
222
- prompt: prompt.slice(0, 4000),
223
- referenceDataUrl: typeof referenceDataUrl === "string" ? referenceDataUrl : undefined,
224
- });
225
- const persisted = setStyleSheet(req.params.id, sheet);
226
- if (!persisted) {
227
- return res.status(404).json({
228
- error: { code: "SESSION_NOT_FOUND", message: "Session not found" },
229
- });
230
- }
231
- logEvent("session", "stylesheet_extract_done", {
232
- sessionId: req.params.id,
233
- sheetChars: safeJsonChars(sheet),
234
- });
235
- res.json({ styleSheet: sheet });
236
- } catch (e) {
237
- const err = errInfo(e);
238
- const code = err.code || "STYLE_SHEET_ERROR";
239
- const status =
240
- code === "STYLE_SHEET_NO_KEY" || code === "STYLE_SHEET_BAD_INPUT"
241
- ? 400
242
- : code === "STYLE_SHEET_EMPTY" || code === "STYLE_SHEET_PARSE" || code === "STYLE_SHEET_SHAPE"
243
- ? 422
244
- : 500;
245
- logError("session", "stylesheet_extract_error", err.raw, { sessionId: req.params.id, code });
246
- res.status(status).json({ error: { code, message: err.message } });
247
- }
248
- });
249
-
250
- app.put("/api/sessions/:id/graph", (req: Request<IdParams>, res: Response) => {
251
- try {
252
- const body = (req.body ?? {}) as { nodes?: unknown; edges?: unknown };
253
- const { nodes, edges } = body;
254
- const rawIfMatch = req.get("If-Match");
255
- if (!Array.isArray(nodes) || !Array.isArray(edges)) {
256
- return res.status(400).json({
257
- error: { code: "INVALID_GRAPH", message: "nodes and edges arrays required" },
258
- });
259
- }
260
- if (!rawIfMatch) {
261
- return res.status(428).json({
262
- error: { code: "GRAPH_VERSION_REQUIRED", message: "If-Match header required" },
263
- });
264
- }
265
- if (nodes.length > 500 || edges.length > 1000) {
266
- return res.status(413).json({
267
- error: {
268
- code: "GRAPH_TOO_LARGE",
269
- message: `Graph too large (max 500 nodes / 1000 edges), got ${nodes.length}/${edges.length}`,
270
- },
271
- });
272
- }
273
- const expectedVersion = Number(String(rawIfMatch).replace(/"/g, ""));
274
- if (!Number.isFinite(expectedVersion)) {
275
- return res.status(400).json({
276
- error: { code: "INVALID_GRAPH_VERSION", message: "If-Match must be a finite integer" },
277
- });
278
- }
279
- const saveId = req.get("X-Ima2-Graph-Save-Id") || null;
280
- const saveReason = req.get("X-Ima2-Graph-Save-Reason") || null;
281
- const tabId = req.get("X-Ima2-Tab-Id") || null;
282
- const result = saveGraph(req.params.id, { nodes, edges, expectedVersion });
283
- logEvent("session", "graph_save", {
284
- sessionId: req.params.id,
285
- saveId,
286
- saveReason,
287
- tabId,
288
- nodes: nodes.length,
289
- edges: edges.length,
290
- graphVersion: result.graphVersion,
291
- });
292
- res.json({ ok: true, nodes: nodes.length, edges: edges.length, graphVersion: result.graphVersion });
293
- } catch (e) {
294
- const err = errInfo(e);
295
- const ext = (err.raw && typeof err.raw === "object" ? err.raw as Record<string, unknown> : {});
296
- const code = err.code || "DB_ERROR";
297
- const payload: { error: { code: string; message: string }; currentVersion?: number } = { error: { code, message: err.message } };
298
- if (typeof ext.currentVersion === "number") payload.currentVersion = ext.currentVersion;
299
- if (code === "GRAPH_VERSION_CONFLICT") {
300
- const reqBody = (req.body ?? {}) as { nodes?: unknown; edges?: unknown };
301
- logEvent("session", "graph_conflict", {
302
- sessionId: req.params.id,
303
- saveId: req.get("X-Ima2-Graph-Save-Id") || null,
304
- saveReason: req.get("X-Ima2-Graph-Save-Reason") || null,
305
- tabId: req.get("X-Ima2-Tab-Id") || null,
306
- expectedVersion: Number(String(req.get("If-Match") || "").replace(/"/g, "")),
307
- currentVersion: ext.currentVersion ?? null,
308
- nodes: Array.isArray(reqBody.nodes) ? reqBody.nodes.length : null,
309
- edges: Array.isArray(reqBody.edges) ? reqBody.edges.length : null,
310
- });
311
- } else {
312
- logError("session", "graph_error", err.raw, { sessionId: req.params.id, code });
313
- }
314
- res.status(err.status || 500).json(payload);
315
- }
316
- });
317
- }
package/routes/storage.ts DELETED
@@ -1,47 +0,0 @@
1
- import type { Express, Request, Response } from "express";
2
- import { inspectGeneratedStorage } from "../lib/storageMigration.js";
3
- import { openDirectory } from "../lib/openDirectory.js";
4
- import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
5
-
6
- type StorageStatus = Awaited<ReturnType<typeof inspectGeneratedStorage>>;
7
-
8
- export function registerStorageRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
9
- const ctx = requireRuntimeContext(ctxRaw);
10
- app.get("/api/storage/status", async (_req: Request, res: Response) => {
11
- const status = await inspectGeneratedStorage(ctx);
12
- res.json({
13
- ok: true,
14
- data: toPublicStorageStatus(status),
15
- });
16
- });
17
-
18
- app.post("/api/storage/open-generated-dir", async (_req: Request, res: Response) => {
19
- const result = (await openDirectory(ctx.config.storage.generatedDir)) as {
20
- ok: boolean;
21
- error?: string;
22
- };
23
- if (result.ok) return res.json({ ok: true });
24
- return res.status(500).json({
25
- ok: false,
26
- error: {
27
- code: "OPEN_GENERATED_DIR_FAILED",
28
- message: result.error || "Could not open generated image folder.",
29
- },
30
- });
31
- });
32
- }
33
-
34
- function toPublicStorageStatus(status: StorageStatus) {
35
- return {
36
- generatedDirLabel: status.generatedDirLabel,
37
- generatedCount: status.targetFileCount,
38
- legacyCandidatesScanned: status.legacyCandidatesScanned,
39
- legacySourcesFound: status.legacySourcesFound,
40
- legacyFilesFound: status.legacyFilesFound,
41
- state: status.state,
42
- messageKind: status.messageKind,
43
- recoveryDocsPath: status.recoveryDocsPath,
44
- doctorCommand: status.doctorCommand,
45
- overrides: status.overrides,
46
- };
47
- }
package/routes/video.ts DELETED
@@ -1,300 +0,0 @@
1
- import { mkdir, readFile, unlink, writeFile } from "fs/promises";
2
- import { join } from "path";
3
- import { randomBytes } from "crypto";
4
- import type { Express, Request, Response } from "express";
5
- import { startJob, finishJob, registerJobAbortController, isJobCanceled, setJobPhase } from "../lib/inflight.js";
6
- import { isGenerationCanceledError, makeGenerationCanceledError } from "../lib/generationCancel.js";
7
- import { logEvent, logError } from "../lib/logger.js";
8
- import { invalidateHistoryIndex } from "../lib/historyIndex.js";
9
- import { generateVideoViaGrok, type GrokVideoEvent } from "../lib/grokVideoAdapter.js";
10
- import { getVideoSeriesChain } from "../lib/videoSeriesChain.js";
11
- import {
12
- ACTIVE_VIDEO_PROMPT_GUIDANCE,
13
- appendVideoContinuityEntry,
14
- lineageFromVideoMetadata,
15
- normalizeVideoContinuityLineage,
16
- readVideoSidecar,
17
- requireActiveVideoPrompt,
18
- safeGeneratedVideoFilename,
19
- type VideoContinuityLineage,
20
- } from "../lib/videoContinuity.js";
21
- import { extractGeneratedVideoFrameB64 } from "../lib/videoFrameExtract.js";
22
- import {
23
- normalizeGrokVideoModel,
24
- normalizeVideoResolution,
25
- normalizeVideoAspectRatio,
26
- normalizeVideoDuration,
27
- deriveVideoMode,
28
- clampVideoDuration,
29
- MAX_REF2V_REFERENCES,
30
- type VideoMode,
31
- } from "../lib/imageModels.js";
32
- import { errInfo } from "../lib/errInfo.js";
33
- import { requireRuntimeContext, type RouteRuntimeContext, type RuntimeContext } from "../lib/runtimeContext.js";
34
-
35
- function sendSse(res: Response, event: string, data: unknown) {
36
- res.write(`event: ${event}\n`);
37
- res.write(`data: ${JSON.stringify(data)}\n\n`);
38
- }
39
-
40
- function toArray(v: unknown): unknown[] {
41
- return Array.isArray(v) ? v : [];
42
- }
43
-
44
- type NormalizeError = { error: string; code: string; status: number };
45
-
46
- function isNormalizeError(x: unknown): x is NormalizeError {
47
- return typeof x === "object" && x !== null && typeof (x as { error?: unknown }).error === "string";
48
- }
49
-
50
- export async function saveGeneratedVideoArtifact(ctx: RuntimeContext, filename: string, buffer: Buffer, metadata: unknown): Promise<void> {
51
- const filePath = join(ctx.config.storage.generatedDir, filename);
52
- await writeFile(filePath, buffer);
53
- try {
54
- await writeFile(`${filePath}.json`, JSON.stringify(metadata));
55
- } catch (err) {
56
- await unlink(filePath).catch(() => {});
57
- throw err;
58
- }
59
- }
60
-
61
- async function resolveSourceImage(
62
- ctx: RuntimeContext,
63
- sourceImage: unknown,
64
- sourceFilename: unknown,
65
- ): Promise<{ b64: string | null; filename: string | null }> {
66
- if (typeof sourceFilename === "string" && sourceFilename) {
67
- const safe = sourceFilename.replace(/^\/+/, "");
68
- if (safe.includes("..")) throw { status: 400, code: "GROK_VIDEO_INVALID_MODE", message: "invalid source filename" };
69
- if (/\.mp4$/i.test(safe)) throw { status: 400, code: "GROK_VIDEO_INVALID_MODE", message: "use continueFromVideo for generated video continuation" };
70
- const buf = await readFile(join(ctx.config.storage.generatedDir, safe));
71
- return { b64: buf.toString("base64"), filename: safe };
72
- }
73
- if (typeof sourceImage === "string" && sourceImage) {
74
- return { b64: sourceImage, filename: null };
75
- }
76
- return { b64: null, filename: null };
77
- }
78
-
79
- export function registerVideoRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
80
- const ctx = requireRuntimeContext(ctxRaw);
81
- app.post("/api/video/generate", async (req: Request, res: Response) => {
82
- const requestId =
83
- typeof req.body?.requestId === "string"
84
- ? req.body.requestId
85
- : typeof req.body?.clientRequestId === "string"
86
- ? req.body.clientRequestId
87
- : req.id;
88
- let finishStatus = "completed";
89
- let finishHttpStatus = 200;
90
- let finishErrorCode: string | undefined;
91
- let finishMeta: Record<string, unknown> = {};
92
- let finishCanceled = false;
93
- const cancelController = new AbortController();
94
-
95
- res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
96
- res.setHeader("Cache-Control", "no-cache, no-transform");
97
- res.setHeader("Connection", "keep-alive");
98
- res.flushHeaders?.();
99
-
100
- const fail = (status: number | undefined, code: string, error: string, extra: Record<string, unknown> = {}) => {
101
- const httpStatus = status ?? 500;
102
- finishStatus = "error";
103
- finishHttpStatus = httpStatus;
104
- finishErrorCode = code;
105
- sendSse(res, "error", { error, code, status: httpStatus, requestId, ...extra });
106
- };
107
-
108
- try {
109
- const { prompt, provider = "grok", model: rawModel } = req.body || {};
110
- const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId : null;
111
- const clientNodeId = typeof req.body?.clientNodeId === "string" ? req.body.clientNodeId : null;
112
- const topic = typeof req.body?.topic === "string" ? req.body.topic.trim() : "";
113
-
114
- if (provider !== "grok") return fail(400, "VIDEO_PROVIDER_UNSUPPORTED", "video generation requires provider 'grok'");
115
- const activePrompt = requireActiveVideoPrompt(prompt);
116
- if (!activePrompt) return fail(400, "PROMPT_REQUIRED", "Prompt is required", { guidance: ACTIVE_VIDEO_PROMPT_GUIDANCE });
117
-
118
- const modelCheck = normalizeGrokVideoModel(rawModel);
119
- if (isNormalizeError(modelCheck)) return fail(modelCheck.status, modelCheck.code, modelCheck.error);
120
- const durationCheck = normalizeVideoDuration(req.body?.duration);
121
- if (isNormalizeError(durationCheck)) return fail(durationCheck.status, durationCheck.code, durationCheck.error);
122
- const resolutionCheck = normalizeVideoResolution(req.body?.resolution);
123
- if (isNormalizeError(resolutionCheck)) return fail(resolutionCheck.status, resolutionCheck.code, resolutionCheck.error);
124
- const aspectCheck = normalizeVideoAspectRatio(req.body?.aspectRatio);
125
- if (isNormalizeError(aspectCheck)) return fail(aspectCheck.status, aspectCheck.code, aspectCheck.error);
126
-
127
- // Resolve reference inputs: base64 list + existing-file list + legacy single source.
128
- let parentLineage: VideoContinuityLineage | null = null;
129
- let continueFromVideoFilename: string | null = null;
130
- if (typeof req.body?.continueFromVideo === "string" && req.body.continueFromVideo.trim()) {
131
- try {
132
- continueFromVideoFilename = safeGeneratedVideoFilename(req.body.continueFromVideo);
133
- const parentMeta = await readVideoSidecar(ctx.config.storage.generatedDir, continueFromVideoFilename);
134
- parentLineage = lineageFromVideoMetadata(continueFromVideoFilename, parentMeta);
135
- } catch (e: any) {
136
- return fail(e?.status || 400, "GROK_VIDEO_INVALID_MODE", e?.message || "invalid continuation video");
137
- }
138
- } else {
139
- parentLineage = normalizeVideoContinuityLineage(req.body?.continuityLineage);
140
- }
141
-
142
- const refInputs: Array<{ image?: unknown; filename?: unknown }> = [
143
- ...toArray(req.body?.referenceImages).map((image) => ({ image })),
144
- ...toArray(req.body?.referenceFilenames).map((filename) => ({ filename })),
145
- ...(req.body?.sourceImage || req.body?.sourceFilename
146
- ? [{ image: req.body?.sourceImage, filename: req.body?.sourceFilename }]
147
- : []),
148
- ];
149
- if (continueFromVideoFilename && !req.body?.sourceImage && !req.body?.sourceFilename) {
150
- try {
151
- refInputs.push({ image: await extractGeneratedVideoFrameB64(ctx.config.storage.generatedDir, continueFromVideoFilename) });
152
- } catch (e: any) {
153
- return fail(e?.status || 500, "GROK_VIDEO_FRAME_FAILED", e?.message || "failed to extract continuation frame");
154
- }
155
- }
156
- let resolved: Array<{ b64: string; filename: string | null }>;
157
- try {
158
- const all = await Promise.all(refInputs.map((r) => resolveSourceImage(ctx, r.image, r.filename)));
159
- resolved = all.filter((r): r is { b64: string; filename: string | null } => Boolean(r.b64));
160
- } catch (e: any) {
161
- return fail(e?.status || 400, e?.code || "GROK_VIDEO_INVALID_MODE", e?.message || "invalid reference image");
162
- }
163
- if (resolved.length > MAX_REF2V_REFERENCES) return fail(400, "GROK_VIDEO_REF_TOO_MANY", `at most ${MAX_REF2V_REFERENCES} reference images`);
164
- const mode: VideoMode = deriveVideoMode(resolved.length);
165
- const duration = clampVideoDuration(durationCheck.duration, mode);
166
- const referenceImages = mode === "reference-to-video" ? resolved.map((r) => r.b64) : undefined;
167
- const sourceB64 = mode === "image-to-video" ? resolved[0]?.b64 : undefined;
168
- const sourceFilename = resolved[0]?.filename ?? null;
169
-
170
- startJob({
171
- requestId,
172
- kind: "video",
173
- prompt: activePrompt,
174
- meta: { kind: "video", sessionId, clientNodeId, model: modelCheck.model, mode, duration, resolution: resolutionCheck.resolution },
175
- });
176
- registerJobAbortController(requestId, cancelController);
177
- await mkdir(ctx.config.storage.generatedDir, { recursive: true });
178
-
179
- logEvent("video", "request", { requestId, mode, duration, resolution: resolutionCheck.resolution, aspectRatio: aspectCheck.aspectRatio });
180
- const startTime = Date.now();
181
-
182
- const onEvent = (ev: GrokVideoEvent) => {
183
- if (ev.phase === "submitted") {
184
- setJobPhase(requestId, "streaming");
185
- sendSse(res, "submitted", {
186
- requestId,
187
- xaiVideoRequestId: ev.xaiVideoRequestId,
188
- requestedModel: ev.requestedModel,
189
- effectiveModel: ev.effectiveModel,
190
- modelFallback: ev.modelFallback ?? null,
191
- });
192
- } else if (ev.phase === "progress") {
193
- sendSse(res, "progress", { requestId, progress: typeof ev.progress === "number" ? ev.progress / 100 : null, stalled: Boolean(ev.stalled) });
194
- } else {
195
- setJobPhase(requestId, "planning");
196
- sendSse(res, "planning", { requestId });
197
- }
198
- };
199
-
200
- // Build prompt with series chain context
201
- const chain = !parentLineage && topic ? await getVideoSeriesChain(ctx.config.storage.generatedDir, topic) : [];
202
- const effectivePrompt = chain.length > 0
203
- ? `[Series topic: ${topic}]\n[Previous prompts in series:\n${chain.map((p, i) => `${i + 1}. ${p}`).join("\n")}\n]\n\n${activePrompt}`
204
- : activePrompt;
205
-
206
- const result = await generateVideoViaGrok(effectivePrompt, ctx, {
207
- model: modelCheck.model,
208
- mode,
209
- duration,
210
- resolution: resolutionCheck.resolution,
211
- aspectRatio: aspectCheck.aspectRatio,
212
- sourceImage: sourceB64,
213
- referenceImages,
214
- signal: cancelController.signal,
215
- requestId,
216
- continuityLineage: parentLineage,
217
- onEvent,
218
- });
219
-
220
- const rand = randomBytes(ctx.config.ids.generatedHexBytes).toString("hex");
221
- const filename = `${Date.now()}_${rand}.mp4`;
222
- const elapsed = +((Date.now() - startTime) / 1000).toFixed(1);
223
- const videoContinuity = appendVideoContinuityEntry(parentLineage, {
224
- filename,
225
- userPrompt: activePrompt,
226
- revisedPrompt: result.revisedPrompt,
227
- createdAt: Date.now(),
228
- });
229
- const meta = {
230
- kind: "video",
231
- mediaType: "video",
232
- requestId,
233
- sessionId,
234
- clientNodeId,
235
- prompt: activePrompt,
236
- userPrompt: activePrompt,
237
- revisedPrompt: result.revisedPrompt,
238
- provider: "grok",
239
- model: result.effectiveModel,
240
- requestedModel: result.requestedModel,
241
- effectiveModel: result.effectiveModel,
242
- modelFallback: result.modelFallback,
243
- createdAt: Date.now(),
244
- elapsed,
245
- usage: result.usage,
246
- webSearchCalls: result.webSearchCalls,
247
- video: {
248
- duration: result.duration,
249
- resolution: result.resolution,
250
- aspectRatio: result.aspectRatio,
251
- sourceImageFilename: sourceFilename,
252
- xaiVideoRequestId: result.xaiVideoRequestId,
253
- requestedModel: result.requestedModel,
254
- effectiveModel: result.effectiveModel,
255
- modelFallback: result.modelFallback,
256
- },
257
- videoContinuity,
258
- ...(topic ? { videoSeries: { topic, chainIndex: chain.length } } : {}),
259
- };
260
- await saveGeneratedVideoArtifact(ctx, filename, result.videoBuffer, meta);
261
- invalidateHistoryIndex();
262
-
263
- finishMeta = { filename, xaiVideoRequestId: result.xaiVideoRequestId };
264
- logEvent("video", "saved", { requestId, filename, bytes: result.videoBuffer.length, elapsedMs: Date.now() - startTime });
265
- sendSse(res, "done", {
266
- requestId,
267
- filename,
268
- url: `/generated/${encodeURIComponent(filename)}`,
269
- mediaType: "video",
270
- revisedPrompt: result.revisedPrompt,
271
- elapsed,
272
- usage: result.usage,
273
- requestedModel: result.requestedModel,
274
- effectiveModel: result.effectiveModel,
275
- modelFallback: result.modelFallback,
276
- video: meta.video,
277
- videoContinuity,
278
- ...(meta.videoSeries ? { videoSeries: meta.videoSeries } : {}),
279
- });
280
- } catch (e) {
281
- const err = errInfo(e);
282
- if (isGenerationCanceledError(err.raw) || isJobCanceled(requestId)) {
283
- const canceled = makeGenerationCanceledError();
284
- finishCanceled = true;
285
- finishHttpStatus = canceled.status;
286
- finishErrorCode = canceled.code;
287
- sendSse(res, "error", { error: canceled.message, code: canceled.code, status: canceled.status, requestId });
288
- } else {
289
- finishStatus = "error";
290
- finishHttpStatus = err.status || 500;
291
- finishErrorCode = err.code || "GROK_VIDEO_FAILED";
292
- logError("video", "error", err.raw, { requestId, code: finishErrorCode });
293
- sendSse(res, "error", { error: err.message, code: finishErrorCode, status: finishHttpStatus, requestId });
294
- }
295
- } finally {
296
- finishJob(requestId, { canceled: finishCanceled, status: finishStatus, httpStatus: finishHttpStatus, errorCode: finishErrorCode, meta: finishMeta });
297
- res.end();
298
- }
299
- });
300
- }