ima2-gen 1.1.21 → 1.1.22

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 (208) hide show
  1. package/README.md +30 -4
  2. package/bin/ima2.js +14 -4
  3. package/bin/lib/platform.js +34 -5
  4. package/docs/README.ko.md +31 -0
  5. package/lib/agentQueueWorker.js +6 -0
  6. package/lib/agentRuntime.js +3 -2
  7. package/lib/atomicWrite.js +14 -0
  8. package/lib/grokProxyLauncher.js +5 -3
  9. package/lib/inflight.js +1 -1
  10. package/lib/oauthLauncher.js +5 -0
  11. package/lib/videoFrameExtract.js +3 -3
  12. package/package.json +5 -7
  13. package/routes/edit.js +2 -1
  14. package/routes/generate.js +4 -3
  15. package/routes/health.js +4 -3
  16. package/routes/multimode.js +2 -1
  17. package/routes/video.js +4 -2
  18. package/server.js +29 -2
  19. package/ui/dist/.vite/manifest.json +12 -12
  20. package/ui/dist/assets/{AgentWorkspace-B_hq9CLg.js → AgentWorkspace-COxQ5TjU.js} +1 -1
  21. package/ui/dist/assets/{CardNewsWorkspace-wD12J7qk.js → CardNewsWorkspace-B0OkcuVz.js} +1 -1
  22. package/ui/dist/assets/{NodeCanvas-CI_wuPMf.js → NodeCanvas-BSsclEBh.js} +1 -1
  23. package/ui/dist/assets/{PromptBuilderPanel-CUTujJUV.js → PromptBuilderPanel-DpC9A5Rz.js} +1 -1
  24. package/ui/dist/assets/{PromptImportDialog-CUi66jPK.js → PromptImportDialog-CVwT0rLd.js} +2 -2
  25. package/ui/dist/assets/{PromptImportDiscoverySection-Cm3vrjY4.js → PromptImportDiscoverySection-BDCkRCRs.js} +1 -1
  26. package/ui/dist/assets/{PromptImportFolderSection-DOtWTD9n.js → PromptImportFolderSection-QoKbZD83.js} +1 -1
  27. package/ui/dist/assets/{PromptLibraryPanel-BMjQegRa.js → PromptLibraryPanel-BhFgeKnY.js} +2 -2
  28. package/ui/dist/assets/SettingsWorkspace-CfjrlH5R.js +1 -0
  29. package/ui/dist/assets/index-C-mur7pa.css +1 -0
  30. package/ui/dist/assets/index-CCP5nUOj.js +42 -0
  31. package/ui/dist/assets/{index-31uVIdt4.js → index-Cxhzi3bs.js} +1 -1
  32. package/ui/dist/index.html +2 -2
  33. package/bin/commands/annotate.ts +0 -119
  34. package/bin/commands/cancel.ts +0 -48
  35. package/bin/commands/canvas-versions.ts +0 -80
  36. package/bin/commands/capabilities.ts +0 -110
  37. package/bin/commands/cardnews.ts +0 -249
  38. package/bin/commands/comfy.ts +0 -54
  39. package/bin/commands/config.ts +0 -186
  40. package/bin/commands/defaults.ts +0 -192
  41. package/bin/commands/doctor.ts +0 -202
  42. package/bin/commands/edit.ts +0 -150
  43. package/bin/commands/gen.ts +0 -214
  44. package/bin/commands/grok.ts +0 -90
  45. package/bin/commands/history.ts +0 -146
  46. package/bin/commands/ls.ts +0 -64
  47. package/bin/commands/metadata.ts +0 -39
  48. package/bin/commands/multimode.ts +0 -196
  49. package/bin/commands/node.ts +0 -166
  50. package/bin/commands/observability.ts +0 -176
  51. package/bin/commands/ping.ts +0 -31
  52. package/bin/commands/prompt-sub/build.ts +0 -101
  53. package/bin/commands/prompt.ts +0 -492
  54. package/bin/commands/ps.ts +0 -81
  55. package/bin/commands/session.ts +0 -266
  56. package/bin/commands/show.ts +0 -72
  57. package/bin/commands/skill.ts +0 -70
  58. package/bin/commands/video.ts +0 -442
  59. package/bin/ima2.ts +0 -430
  60. package/bin/lib/args.ts +0 -92
  61. package/bin/lib/browser-id.ts +0 -16
  62. package/bin/lib/client.ts +0 -122
  63. package/bin/lib/config-store.ts +0 -120
  64. package/bin/lib/destructive-confirm.ts +0 -19
  65. package/bin/lib/doctor-checks.ts +0 -91
  66. package/bin/lib/error-hints.ts +0 -23
  67. package/bin/lib/files.ts +0 -39
  68. package/bin/lib/output.ts +0 -73
  69. package/bin/lib/platform.ts +0 -99
  70. package/bin/lib/recover-output.ts +0 -139
  71. package/bin/lib/sse.ts +0 -73
  72. package/bin/lib/star-prompt.ts +0 -97
  73. package/bin/lib/storage-doctor.ts +0 -39
  74. package/bin/lib/ui-build.ts +0 -85
  75. package/config.ts +0 -354
  76. package/lib/agentCommandParser.ts +0 -69
  77. package/lib/agentGenerationPlanner.ts +0 -273
  78. package/lib/agentQuestionResponder.ts +0 -266
  79. package/lib/agentQueueStore.ts +0 -270
  80. package/lib/agentQueueWorker.ts +0 -89
  81. package/lib/agentRuntime.ts +0 -604
  82. package/lib/agentSettings.ts +0 -72
  83. package/lib/agentStore.ts +0 -422
  84. package/lib/agentStoreRows.ts +0 -136
  85. package/lib/agentTypes.ts +0 -154
  86. package/lib/apiCachePolicy.ts +0 -11
  87. package/lib/assetLifecycle.ts +0 -146
  88. package/lib/canvasVersionStore.ts +0 -223
  89. package/lib/capabilities.ts +0 -126
  90. package/lib/cardNewsGenerator.ts +0 -271
  91. package/lib/cardNewsJobStore.ts +0 -142
  92. package/lib/cardNewsManifestStore.ts +0 -154
  93. package/lib/cardNewsPlanner.ts +0 -236
  94. package/lib/cardNewsPlannerClient.ts +0 -155
  95. package/lib/cardNewsPlannerPrompt.ts +0 -62
  96. package/lib/cardNewsPlannerSchema.ts +0 -321
  97. package/lib/cardNewsRoleTemplateStore.ts +0 -47
  98. package/lib/cardNewsTemplateStore.ts +0 -252
  99. package/lib/codexDetect.ts +0 -71
  100. package/lib/comfyBridge.ts +0 -235
  101. package/lib/composerSnapshot.ts +0 -33
  102. package/lib/configKeys.ts +0 -62
  103. package/lib/db.ts +0 -295
  104. package/lib/errInfo.ts +0 -43
  105. package/lib/errorClassify.ts +0 -100
  106. package/lib/generationCancel.ts +0 -28
  107. package/lib/generationErrors.ts +0 -238
  108. package/lib/grokImageAdapter.ts +0 -513
  109. package/lib/grokMultimodeAdapter.ts +0 -84
  110. package/lib/grokProxyLauncher.ts +0 -153
  111. package/lib/grokRuntime.ts +0 -23
  112. package/lib/grokSizeMapper.ts +0 -71
  113. package/lib/grokVideoAdapter.ts +0 -458
  114. package/lib/grokVideoCanvas.ts +0 -26
  115. package/lib/grokVideoDownload.ts +0 -59
  116. package/lib/grokVideoPlannerPrompt.ts +0 -67
  117. package/lib/historyIndex.ts +0 -51
  118. package/lib/historyList.ts +0 -181
  119. package/lib/imageMetadata.ts +0 -113
  120. package/lib/imageMetadataStore.ts +0 -67
  121. package/lib/imageModels.ts +0 -165
  122. package/lib/inflight.ts +0 -281
  123. package/lib/localImportStore.ts +0 -114
  124. package/lib/logger.ts +0 -161
  125. package/lib/nodeStore.ts +0 -91
  126. package/lib/oauthLauncher.ts +0 -94
  127. package/lib/oauthNormalize.ts +0 -30
  128. package/lib/oauthProxy/errors.ts +0 -128
  129. package/lib/oauthProxy/generators.ts +0 -494
  130. package/lib/oauthProxy/index.ts +0 -28
  131. package/lib/oauthProxy/prompts.ts +0 -123
  132. package/lib/oauthProxy/references.ts +0 -45
  133. package/lib/oauthProxy/runtime.ts +0 -115
  134. package/lib/oauthProxy/streams.ts +0 -232
  135. package/lib/oauthProxy/types.ts +0 -9
  136. package/lib/oauthProxy.ts +0 -3
  137. package/lib/openDirectory.ts +0 -47
  138. package/lib/pngInfo.ts +0 -26
  139. package/lib/promptBuilder/attachments.ts +0 -74
  140. package/lib/promptBuilder/client.ts +0 -130
  141. package/lib/promptBuilder/constants.ts +0 -9
  142. package/lib/promptBuilder/context.ts +0 -36
  143. package/lib/promptBuilder/errors.ts +0 -12
  144. package/lib/promptBuilder/requestSchema.ts +0 -56
  145. package/lib/promptBuilder/responseParser.ts +0 -219
  146. package/lib/promptBuilder/systemPrompt.ts +0 -135
  147. package/lib/promptBuilder/transport.ts +0 -94
  148. package/lib/promptBuilder/types.ts +0 -109
  149. package/lib/promptImport/curatedSources.ts +0 -141
  150. package/lib/promptImport/discoveryRegistry.ts +0 -329
  151. package/lib/promptImport/errors.ts +0 -18
  152. package/lib/promptImport/githubDiscovery.ts +0 -309
  153. package/lib/promptImport/githubFolder.ts +0 -397
  154. package/lib/promptImport/githubSource.ts +0 -257
  155. package/lib/promptImport/gptImageHints.ts +0 -70
  156. package/lib/promptImport/parsePromptCandidates.ts +0 -179
  157. package/lib/promptImport/promptIndex.ts +0 -326
  158. package/lib/promptImport/rankPromptCandidates.ts +0 -65
  159. package/lib/promptImport/types.ts +0 -103
  160. package/lib/promptSafetyPolicy.ts +0 -5
  161. package/lib/providerOptions.ts +0 -56
  162. package/lib/referenceImageCompress.ts +0 -84
  163. package/lib/refs.ts +0 -133
  164. package/lib/requestLogger.ts +0 -49
  165. package/lib/responsesDoctor.ts +0 -456
  166. package/lib/responsesErrors.ts +0 -83
  167. package/lib/responsesFallback.ts +0 -114
  168. package/lib/responsesImageAdapter.ts +0 -466
  169. package/lib/responsesParse.ts +0 -452
  170. package/lib/responsesTools.ts +0 -28
  171. package/lib/runtimeContext.ts +0 -146
  172. package/lib/runtimePorts.ts +0 -105
  173. package/lib/sessionStore.ts +0 -308
  174. package/lib/storageMigration.ts +0 -310
  175. package/lib/styleSheet.ts +0 -139
  176. package/lib/systemTrash.ts +0 -20
  177. package/lib/videoContinuity.ts +0 -180
  178. package/lib/videoFrameExtract.ts +0 -78
  179. package/lib/videoSeriesChain.ts +0 -29
  180. package/lib/visibleTextLanguagePolicy.ts +0 -7
  181. package/routes/agent.ts +0 -308
  182. package/routes/annotations.ts +0 -118
  183. package/routes/canvasVersions.ts +0 -69
  184. package/routes/capabilities.ts +0 -18
  185. package/routes/cardNews.ts +0 -211
  186. package/routes/comfy.ts +0 -43
  187. package/routes/edit.ts +0 -352
  188. package/routes/generate.ts +0 -492
  189. package/routes/grok.ts +0 -24
  190. package/routes/health.ts +0 -123
  191. package/routes/history.ts +0 -221
  192. package/routes/imageImport.ts +0 -37
  193. package/routes/index.ts +0 -52
  194. package/routes/metadata.ts +0 -77
  195. package/routes/multimode.ts +0 -499
  196. package/routes/nodes.ts +0 -578
  197. package/routes/promptBuilder.ts +0 -37
  198. package/routes/promptImport.ts +0 -379
  199. package/routes/prompts.ts +0 -428
  200. package/routes/quota.ts +0 -89
  201. package/routes/sessions.ts +0 -317
  202. package/routes/storage.ts +0 -47
  203. package/routes/video.ts +0 -300
  204. package/routes/videoExtended.ts +0 -284
  205. package/server.ts +0 -293
  206. package/ui/dist/assets/SettingsWorkspace-PiaVnsdA.js +0 -1
  207. package/ui/dist/assets/index-CjgnNtgt.css +0 -1
  208. package/ui/dist/assets/index-Da2s4_-5.js +0 -36
@@ -1,78 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { randomBytes } from "node:crypto";
3
- import { open, readFile, realpath, stat, unlink } from "node:fs/promises";
4
- import { extname, join, resolve, sep } from "node:path";
5
- import { promisify } from "node:util";
6
-
7
- const execFileAsync = promisify(execFile);
8
- const MAX_LOCAL_VIDEO_BYTES = 100 * 1024 * 1024;
9
- const MAX_FRAME_POSITION_SECONDS = 60 * 60;
10
- const FFMPEG_TIMEOUT_MS = 30_000;
11
-
12
- function routeError(message: string, status = 400): Error & { status: number } {
13
- return Object.assign(new Error(message), { status });
14
- }
15
-
16
- export async function safeGeneratedFilePath(generatedDir: string, file: string, options: { requireMp4?: boolean } = {}): Promise<string> {
17
- const base = resolve(generatedDir);
18
- const target = file.startsWith("/") ? resolve(file) : resolve(base, file);
19
- if (target !== base && !target.startsWith(`${base}${sep}`)) {
20
- throw routeError("invalid file path", 400);
21
- }
22
- let baseReal: string;
23
- let targetReal: string;
24
- try {
25
- baseReal = await realpath(base);
26
- targetReal = await realpath(target);
27
- } catch {
28
- throw routeError("video file not found", 404);
29
- }
30
- if (targetReal !== baseReal && !targetReal.startsWith(`${baseReal}${sep}`)) {
31
- throw routeError("invalid file path", 400);
32
- }
33
- if (options.requireMp4 && extname(targetReal).toLowerCase() !== ".mp4") {
34
- throw routeError("generated video input must be an .mp4 file", 400);
35
- }
36
- return targetReal;
37
- }
38
-
39
- export async function assertLocalMp4(path: string): Promise<void> {
40
- const info = await stat(path);
41
- if (!info.isFile()) throw routeError("generated video input must be a file", 400);
42
- if (info.size <= 0) throw routeError("generated video input is empty", 400);
43
- if (info.size > MAX_LOCAL_VIDEO_BYTES) throw routeError("generated video input exceeds the 100MB limit", 400);
44
- const fh = await open(path, "r");
45
- try {
46
- const header = Buffer.alloc(12);
47
- const { bytesRead } = await fh.read(header, 0, header.length, 0);
48
- if (bytesRead < 12 || header.subarray(4, 8).toString("ascii") !== "ftyp") {
49
- throw routeError("generated video input must be an MP4 container", 400);
50
- }
51
- } finally {
52
- await fh.close();
53
- }
54
- }
55
-
56
- export async function extractVideoFrame(input: string, output: string, position: string): Promise<void> {
57
- const options = { timeout: FFMPEG_TIMEOUT_MS, killSignal: "SIGKILL" as const, maxBuffer: 1024 * 1024 };
58
- if (position === "last") {
59
- await execFileAsync("ffmpeg", ["-y", "-sseof", "-3", "-i", input, "-update", "1", "-q:v", "1", output], options);
60
- return;
61
- }
62
- const sec = Number(position);
63
- if (!Number.isFinite(sec) || sec < 0) throw new Error("position must be a non-negative number or 'last'");
64
- if (sec > MAX_FRAME_POSITION_SECONDS) throw new Error("position exceeds the maximum supported seek time");
65
- await execFileAsync("ffmpeg", ["-y", "-ss", String(sec), "-i", input, "-vframes", "1", output], options);
66
- }
67
-
68
- export async function extractGeneratedVideoFrameB64(generatedDir: string, filename: string, position = "last"): Promise<string> {
69
- const inputPath = await safeGeneratedFilePath(generatedDir, filename, { requireMp4: true });
70
- await assertLocalMp4(inputPath);
71
- const tmpOut = join(generatedDir, `frame_tmp_${randomBytes(4).toString("hex")}.png`);
72
- try {
73
- await extractVideoFrame(inputPath, tmpOut, position);
74
- return (await readFile(tmpOut)).toString("base64");
75
- } finally {
76
- await unlink(tmpOut).catch(() => {});
77
- }
78
- }
@@ -1,29 +0,0 @@
1
- import { readdir, readFile } from "fs/promises";
2
- import { join } from "path";
3
-
4
- interface VideoSeriesMeta {
5
- revisedPrompt?: string;
6
- createdAt?: number;
7
- videoSeries?: { topic: string; chainIndex?: number };
8
- }
9
-
10
- /**
11
- * Scan generatedDir for videos with matching topic, return the most recent N revisedPrompts.
12
- */
13
- export async function getVideoSeriesChain(generatedDir: string, topic: string, limit = 4): Promise<string[]> {
14
- if (!topic.trim()) return [];
15
- const entries = await readdir(generatedDir).catch(() => [] as string[]);
16
- const sidecars = entries.filter((e) => e.endsWith(".mp4.json"));
17
- const matches: Array<{ revisedPrompt: string; createdAt: number }> = [];
18
- for (const sidecar of sidecars) {
19
- try {
20
- const raw = await readFile(join(generatedDir, sidecar), "utf-8");
21
- const meta: VideoSeriesMeta = JSON.parse(raw);
22
- if (meta.videoSeries?.topic === topic && meta.revisedPrompt) {
23
- matches.push({ revisedPrompt: meta.revisedPrompt, createdAt: meta.createdAt ?? 0 });
24
- }
25
- } catch { /* skip unreadable */ }
26
- }
27
- matches.sort((a, b) => b.createdAt - a.createdAt);
28
- return matches.slice(0, limit).reverse().map((m) => m.revisedPrompt);
29
- }
@@ -1,7 +0,0 @@
1
- export const VISIBLE_TEXT_LANGUAGE_POLICY = [
2
- "Visible text and language rule:",
3
- "If the image must contain readable text, signage, labels, UI copy, captions, slogans, typography, or keywords in a specific language, explicitly list the exact visible words in that language and script.",
4
- "Do not translate, romanize, summarize, substitute, or invent alternate wording.",
5
- "Do not use vague placeholders such as \"Korean text\", \"Japanese words\", or \"foreign language text\".",
6
- "If exact words are needed, state them as exact visible text items and render only those listed items.",
7
- ].join(" ");
package/routes/agent.ts DELETED
@@ -1,308 +0,0 @@
1
- import type { Express, Request, Response } from "express";
2
- import {
3
- appendAgentTurn,
4
- compactAgentSession,
5
- createAgentSession,
6
- deleteAgentSession,
7
- getAgentGenerationSettings,
8
- getAgentSession,
9
- getAgentWorkspacePayload,
10
- renameAgentSession,
11
- setAgentCurrentImage,
12
- setAgentGenerationSettings,
13
- setAgentLocks,
14
- setAgentWebSearch,
15
- } from "../lib/agentStore.js";
16
- import {
17
- cancelAgentQueueItem,
18
- createAgentQueueItem,
19
- getAgentQueueItem,
20
- listAgentQueueItems,
21
- retryAgentQueueItem,
22
- } from "../lib/agentQueueStore.js";
23
- import { ensureAgentQueueWorker, tickAgentQueueWorker } from "../lib/agentQueueWorker.js";
24
- import { parseAgentSlashCommand, formatAgentQuestionReply, formatAgentSlashHelp } from "../lib/agentCommandParser.js";
25
- import { requestAgentQuestionAnswer } from "../lib/agentQuestionResponder.js";
26
- import { agentAllowedToolPayload, runAgentTurn } from "../lib/agentRuntime.js";
27
- import { errInfo } from "../lib/errInfo.js";
28
- import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
29
-
30
- type AgentSessionBody = {
31
- title?: unknown;
32
- currentImage?: unknown;
33
- webSearchEnabled?: unknown;
34
- currentImageId?: unknown;
35
- styleLocks?: unknown;
36
- subjectLocks?: unknown;
37
- generationSettings?: unknown;
38
- };
39
-
40
- type AgentTurnBody = {
41
- prompt?: unknown;
42
- provider?: unknown;
43
- quality?: unknown;
44
- size?: unknown;
45
- format?: unknown;
46
- moderation?: unknown;
47
- model?: unknown;
48
- reasoningEffort?: unknown;
49
- requestId?: unknown;
50
- };
51
-
52
- type AgentQueueBody = AgentTurnBody & {
53
- options?: unknown;
54
- };
55
-
56
- export function registerAgentRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
57
- const ctx = requireRuntimeContext(ctxRaw);
58
- ensureAgentQueueWorker(ctx);
59
-
60
- app.get("/api/agent/tools", (_req: Request, res: Response) => {
61
- res.json(agentAllowedToolPayload());
62
- });
63
-
64
- app.get("/api/agent/sessions", (req: Request, res: Response) => {
65
- const selectedId = typeof req.query.selectedSessionId === "string" ? req.query.selectedSessionId : null;
66
- res.json(getAgentWorkspacePayload(selectedId));
67
- });
68
-
69
- app.post("/api/agent/sessions", (req: Request, res: Response) => {
70
- try {
71
- const body = (req.body ?? {}) as AgentSessionBody;
72
- const session = createAgentSession({
73
- title: body.title,
74
- currentImage: normalizeCurrentImage(body.currentImage),
75
- webSearchEnabled: body.webSearchEnabled !== false,
76
- });
77
- res.status(201).json(getAgentWorkspacePayload(session.id));
78
- } catch (error) {
79
- sendError(res, error);
80
- }
81
- });
82
-
83
- app.get("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
84
- const session = getAgentSession(req.params.sessionId);
85
- if (!session) return sendError(res, notFound(req.params.sessionId));
86
- res.json(getAgentWorkspacePayload(req.params.sessionId));
87
- });
88
-
89
- app.patch("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
90
- try {
91
- const body = (req.body ?? {}) as AgentSessionBody;
92
- if (Object.prototype.hasOwnProperty.call(body, "title")) renameAgentSession(req.params.sessionId, body.title);
93
- if (typeof body.webSearchEnabled === "boolean") setAgentWebSearch(req.params.sessionId, body.webSearchEnabled);
94
- if (Object.prototype.hasOwnProperty.call(body, "generationSettings")) {
95
- setAgentGenerationSettings(req.params.sessionId, body.generationSettings);
96
- }
97
- if (Object.prototype.hasOwnProperty.call(body, "currentImageId")) {
98
- const ok = setAgentCurrentImage(req.params.sessionId, body.currentImageId);
99
- if (!ok) throw imageNotFound(req.params.sessionId);
100
- }
101
- if (Array.isArray(body.styleLocks) || Array.isArray(body.subjectLocks)) setAgentLocks(req.params.sessionId, body);
102
- res.json(getAgentWorkspacePayload(req.params.sessionId));
103
- } catch (error) {
104
- sendError(res, error);
105
- }
106
- });
107
-
108
- app.delete("/api/agent/sessions/:sessionId", (req: Request<{ sessionId: string }>, res: Response) => {
109
- const ok = deleteAgentSession(req.params.sessionId);
110
- if (!ok) return sendError(res, notFound(req.params.sessionId));
111
- res.json(getAgentWorkspacePayload(null));
112
- });
113
-
114
- app.post("/api/agent/sessions/:sessionId/compact", (req: Request<{ sessionId: string }>, res: Response) => {
115
- try {
116
- if (!getAgentSession(req.params.sessionId)) throw notFound(req.params.sessionId);
117
- compactAgentSession(req.params.sessionId);
118
- res.json(getAgentWorkspacePayload(req.params.sessionId));
119
- } catch (error) {
120
- sendError(res, error);
121
- }
122
- });
123
-
124
- app.get("/api/agent/sessions/:sessionId/manifest", (req: Request<{ sessionId: string }>, res: Response) => {
125
- const payload = getAgentWorkspacePayload(req.params.sessionId);
126
- if (!payload.selectedSessionId) return sendError(res, notFound(req.params.sessionId));
127
- res.type("application/xml").send(payload.manifest ?? "");
128
- });
129
-
130
- app.post("/api/agent/sessions/:sessionId/turns", async (req: Request<{ sessionId: string }>, res: Response) => {
131
- try {
132
- const body = (req.body ?? {}) as AgentTurnBody;
133
- const prompt = cleanPrompt(body.prompt);
134
- await runAgentTurn(ctx, req.params.sessionId, prompt, {
135
- provider: cleanOption(body.provider),
136
- quality: cleanOption(body.quality),
137
- size: cleanOption(body.size),
138
- format: cleanOption(body.format),
139
- moderation: cleanOption(body.moderation),
140
- model: cleanOption(body.model),
141
- reasoningEffort: cleanOption(body.reasoningEffort),
142
- requestId: cleanOption(body.requestId),
143
- });
144
- res.json(getAgentWorkspacePayload(req.params.sessionId));
145
- } catch (error) {
146
- sendError(res, error);
147
- }
148
- });
149
-
150
- app.get("/api/agent/queue", (_req: Request, res: Response) => {
151
- res.json({ queue: listAgentQueueItems() });
152
- });
153
-
154
- app.get("/api/agent/sessions/:sessionId/queue", (req: Request<{ sessionId: string }>, res: Response) => {
155
- if (!getAgentSession(req.params.sessionId)) return sendError(res, notFound(req.params.sessionId));
156
- res.json({ queue: listAgentQueueItems(req.params.sessionId) });
157
- });
158
-
159
- app.post("/api/agent/sessions/:sessionId/queue", async (req: Request<{ sessionId: string }>, res: Response) => {
160
- try {
161
- if (!getAgentSession(req.params.sessionId)) throw notFound(req.params.sessionId);
162
- const body = (req.body ?? {}) as AgentQueueBody;
163
- const rawPrompt = cleanPrompt(body.prompt);
164
- const command = parseAgentSlashCommand(rawPrompt);
165
- appendAgentTurn({ sessionId: req.params.sessionId, role: "user", text: rawPrompt, status: "complete" });
166
- if (command?.name === "help") {
167
- appendAgentTurn({
168
- sessionId: req.params.sessionId,
169
- role: "assistant",
170
- text: formatAgentSlashHelp(),
171
- status: "complete",
172
- });
173
- return res.status(200).json({ queueItem: null, workspace: getAgentWorkspacePayload(req.params.sessionId) });
174
- }
175
- if (command?.name === "question") {
176
- const options = normalizeQueueOptions(req.params.sessionId, body);
177
- const question = command.prompt.trim();
178
- const answer = question
179
- ? (await requestAgentQuestionAnswer(ctx, question, {
180
- provider: cleanOption(options.provider),
181
- model: cleanOption(options.model),
182
- reasoningEffort: cleanOption(options.reasoningEffort),
183
- requestId: cleanOption(body.requestId),
184
- })).text
185
- : formatAgentQuestionReply(command.prompt);
186
- appendAgentTurn({
187
- sessionId: req.params.sessionId,
188
- role: "assistant",
189
- text: answer,
190
- status: "complete",
191
- });
192
- return res.status(200).json({ queueItem: null, workspace: getAgentWorkspacePayload(req.params.sessionId) });
193
- }
194
- const prompt = command ? cleanPrompt(command.prompt) : rawPrompt;
195
- const queueItem = createAgentQueueItem({
196
- sessionId: req.params.sessionId,
197
- prompt,
198
- options: normalizeQueueOptions(req.params.sessionId, body),
199
- command,
200
- });
201
- void tickAgentQueueWorker(ctx);
202
- res.status(202).json({ queueItem, workspace: getAgentWorkspacePayload(req.params.sessionId) });
203
- } catch (error) {
204
- sendError(res, error);
205
- }
206
- });
207
-
208
- app.post("/api/agent/queue/:itemId/cancel", (req: Request<{ itemId: string }>, res: Response) => {
209
- const item = getAgentQueueItem(req.params.itemId);
210
- if (!item) return sendError(res, queueItemNotFound(req.params.itemId));
211
- const ok = cancelAgentQueueItem(item.id);
212
- if (!ok) return sendError(res, queueActionError("AGENT_QUEUE_CANCEL_FAILED", "Only queued Agent work can be canceled."));
213
- res.json(getAgentWorkspacePayload(item.sessionId));
214
- });
215
-
216
- app.post("/api/agent/queue/:itemId/retry", (req: Request<{ itemId: string }>, res: Response) => {
217
- const item = getAgentQueueItem(req.params.itemId);
218
- if (!item) return sendError(res, queueItemNotFound(req.params.itemId));
219
- const ok = retryAgentQueueItem(item.id);
220
- if (!ok) return sendError(res, queueActionError("AGENT_QUEUE_RETRY_FAILED", "Only failed or canceled Agent work can be retried."));
221
- void tickAgentQueueWorker(ctx);
222
- res.json(getAgentWorkspacePayload(item.sessionId));
223
- });
224
- }
225
-
226
- function normalizeCurrentImage(value: unknown) {
227
- if (!value || typeof value !== "object") return null;
228
- const item = value as Record<string, unknown>;
229
- return {
230
- id: cleanOption(item.id),
231
- filename: cleanOption(item.filename),
232
- url: cleanOption(item.url) ?? cleanOption(item.image),
233
- thumbUrl: cleanOption(item.thumbUrl) ?? cleanOption(item.thumb),
234
- prompt: cleanOption(item.prompt) ?? cleanOption(item.userPrompt),
235
- revisedPrompt: cleanOption(item.revisedPrompt),
236
- createdAt: typeof item.createdAt === "number" ? item.createdAt : null,
237
- };
238
- }
239
-
240
- function cleanOption(value: unknown) {
241
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
242
- }
243
-
244
- function cleanPrompt(value: unknown) {
245
- const prompt = cleanOption(value);
246
- if (prompt) return prompt;
247
- const err = new Error("Prompt is required") as Error & { code?: string; status?: number };
248
- err.code = "AGENT_PROMPT_REQUIRED";
249
- err.status = 400;
250
- throw err;
251
- }
252
-
253
- function normalizeQueueOptions(sessionId: string, body: AgentQueueBody) {
254
- const current = getAgentGenerationSettings(sessionId);
255
- const input = body.options && typeof body.options === "object" ? body.options as Record<string, unknown> : {};
256
- return {
257
- ...current,
258
- ...input,
259
- provider: cleanOption(body.provider) ?? input.provider ?? current.provider,
260
- quality: cleanOption(body.quality) ?? input.quality ?? current.quality,
261
- size: cleanOption(body.size) ?? input.size ?? current.size,
262
- format: cleanOption(body.format) ?? input.format ?? current.format,
263
- moderation: cleanOption(body.moderation) ?? input.moderation ?? current.moderation,
264
- model: cleanOption(body.model) ?? input.model ?? current.model,
265
- reasoningEffort: cleanOption(body.reasoningEffort) ?? input.reasoningEffort ?? current.reasoningEffort,
266
- webSearchEnabled: typeof input.webSearchEnabled === "boolean" ? input.webSearchEnabled : current.webSearchEnabled,
267
- generationStrategy: input.generationStrategy ?? current.generationStrategy,
268
- variants: input.variants ?? current.variants,
269
- maxAutoVariants: input.maxAutoVariants ?? current.maxAutoVariants,
270
- parallelism: input.parallelism ?? current.parallelism,
271
- };
272
- }
273
-
274
- function queueActionError(code: string, message: string) {
275
- const err = new Error(message) as Error & { code?: string; status?: number };
276
- err.code = code;
277
- err.status = 409;
278
- return err;
279
- }
280
-
281
- function queueItemNotFound(itemId: string) {
282
- const err = new Error(`Agent queue item not found: ${itemId}`) as Error & { code?: string; status?: number };
283
- err.code = "AGENT_QUEUE_ITEM_NOT_FOUND";
284
- err.status = 404;
285
- return err;
286
- }
287
-
288
- function sendError(res: Response, error: unknown) {
289
- const err = errInfo(error);
290
- res.status(err.status || 500).json({
291
- error: { code: err.code || "AGENT_ERROR", message: err.message },
292
- code: err.code || "AGENT_ERROR",
293
- });
294
- }
295
-
296
- function notFound(sessionId: string) {
297
- const err = new Error(`Agent session not found: ${sessionId}`) as Error & { code?: string; status?: number };
298
- err.code = "AGENT_SESSION_NOT_FOUND";
299
- err.status = 404;
300
- return err;
301
- }
302
-
303
- function imageNotFound(sessionId: string) {
304
- const err = new Error(`Agent image not found in session: ${sessionId}`) as Error & { code?: string; status?: number };
305
- err.code = "AGENT_IMAGE_NOT_FOUND";
306
- err.status = 404;
307
- return err;
308
- }
@@ -1,118 +0,0 @@
1
- import type { Express, Request, Response } from "express";
2
- import { getDb } from "../lib/db.js";
3
-
4
- import { errInfo } from "../lib/errInfo.js";
5
- import type { RouteRuntimeContext } from "../lib/runtimeContext.js";
6
- const MAX_ANNOTATION_PAYLOAD_CHARS = 256 * 1024;
7
-
8
- function getBrowserId(req: Request): string | null {
9
- const browserId = req.headers["x-ima2-browser-id"];
10
- return typeof browserId === "string" && browserId.trim() ? browserId.trim() : null;
11
- }
12
-
13
- function isSafeFilename(filename: unknown): filename is string {
14
- return (
15
- typeof filename === "string" &&
16
- filename.length > 0 &&
17
- filename.length <= 240 &&
18
- !filename.includes("..") &&
19
- !filename.startsWith("/") &&
20
- !filename.includes("\\")
21
- );
22
- }
23
-
24
- interface AnnotationPayload {
25
- paths?: unknown;
26
- boxes?: unknown;
27
- memos?: unknown;
28
- annotations?: AnnotationPayload;
29
- }
30
-
31
- interface NormalizedPayload {
32
- payload?: { paths: unknown[]; boxes: unknown[]; memos: unknown[] };
33
- text?: string;
34
- error?: string;
35
- }
36
-
37
- function normalizePayload(value: unknown): NormalizedPayload {
38
- const v = value as AnnotationPayload | null | undefined;
39
- const payload = (v && typeof v === "object" && "annotations" in v ? v.annotations : v) as
40
- | AnnotationPayload
41
- | null
42
- | undefined;
43
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
44
- return { error: "annotations payload is required" };
45
- }
46
- const paths = Array.isArray(payload.paths) ? payload.paths : [];
47
- const boxes = Array.isArray(payload.boxes) ? payload.boxes : [];
48
- const memos = Array.isArray(payload.memos) ? payload.memos : [];
49
- const normalized = { paths, boxes, memos };
50
- const text = JSON.stringify(normalized);
51
- if (text.length > MAX_ANNOTATION_PAYLOAD_CHARS) {
52
- return { error: "annotations payload is too large" };
53
- }
54
- return { payload: normalized, text };
55
- }
56
-
57
- export function registerAnnotationRoutes(app: Express, _ctx: RouteRuntimeContext) {
58
- app.get("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
59
- try {
60
- const browserId = getBrowserId(req);
61
- const filename = decodeURIComponent(req.params.filename);
62
- if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
63
- if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
64
-
65
- const row = getDb()
66
- .prepare("SELECT payload FROM image_annotations WHERE browser_id = ? AND filename = ?")
67
- .get(browserId, filename) as { payload: string } | undefined;
68
- const annotations = row ? JSON.parse(row.payload) : null;
69
- res.json({ annotations });
70
- } catch (e) {
71
- const err = errInfo(e);
72
- res.status(500).json({ error: err.message });
73
- }
74
- });
75
-
76
- app.put("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
77
- try {
78
- const browserId = getBrowserId(req);
79
- const filename = decodeURIComponent(req.params.filename);
80
- if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
81
- if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
82
-
83
- const normalized = normalizePayload(req.body);
84
- if (normalized.error) return res.status(400).json({ error: normalized.error });
85
-
86
- const id = `${browserId}:${filename}`;
87
- getDb().prepare(`
88
- INSERT INTO image_annotations (id, browser_id, filename, payload, schema_version, updated_at)
89
- VALUES (?, ?, ?, ?, 1, unixepoch())
90
- ON CONFLICT(browser_id, filename) DO UPDATE SET
91
- payload = excluded.payload,
92
- schema_version = excluded.schema_version,
93
- updated_at = unixepoch()
94
- `).run(id, browserId, filename, normalized.text);
95
- res.json({ ok: true });
96
- } catch (e) {
97
- const err = errInfo(e);
98
- res.status(500).json({ error: err.message });
99
- }
100
- });
101
-
102
- app.delete("/api/annotations/:filename", (req: Request<{ filename: string }>, res: Response) => {
103
- try {
104
- const browserId = getBrowserId(req);
105
- const filename = decodeURIComponent(req.params.filename);
106
- if (!browserId) return res.status(400).json({ error: "X-Ima2-Browser-Id header is required" });
107
- if (!isSafeFilename(filename)) return res.status(400).json({ error: "invalid filename" });
108
-
109
- getDb()
110
- .prepare("DELETE FROM image_annotations WHERE browser_id = ? AND filename = ?")
111
- .run(browserId, filename);
112
- res.json({ ok: true });
113
- } catch (e) {
114
- const err = errInfo(e);
115
- res.status(500).json({ error: err.message });
116
- }
117
- });
118
- }
@@ -1,69 +0,0 @@
1
- import express, { type Express, type Request, type Response } from "express";
2
- import { createCanvasVersion, updateCanvasVersion } from "../lib/canvasVersionStore.js";
3
-
4
- import { errInfo } from "../lib/errInfo.js";
5
- import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
6
- function decodeHeader(value: unknown): string | null {
7
- if (typeof value !== "string" || !value) return null;
8
- try {
9
- return decodeURIComponent(value);
10
- } catch {
11
- return value;
12
- }
13
- }
14
-
15
- function getRequestBuffer(req: Request): Buffer {
16
- return Buffer.isBuffer(req.body) ? req.body : Buffer.alloc(0);
17
- }
18
-
19
- function getPrompt(req: Request): string | null {
20
- return decodeHeader(req.headers["x-ima2-canvas-prompt"]);
21
- }
22
-
23
- export function registerCanvasVersionRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
24
- const ctx = requireRuntimeContext(ctxRaw);
25
- const rawPng = express.raw({ type: "image/png", limit: ctx.config.server.bodyLimit });
26
-
27
- app.post("/api/canvas-versions", rawPng, async (req: Request, res: Response) => {
28
- try {
29
- const sourceFilename =
30
- typeof req.query.sourceFilename === "string"
31
- ? req.query.sourceFilename
32
- : decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
33
- const item = await createCanvasVersion(ctx, {
34
- sourceFilename,
35
- prompt: getPrompt(req),
36
- buffer: getRequestBuffer(req),
37
- });
38
- res.status(201).json({ item });
39
- } catch (e) {
40
- const err = errInfo(e);
41
- res.status(err.status || 500).json({
42
- error: err.message,
43
- code: err.code || "CANVAS_VERSION_SAVE_FAILED",
44
- });
45
- }
46
- });
47
-
48
- app.put("/api/canvas-versions/:filename", rawPng, async (req: Request<{ filename: string }>, res: Response) => {
49
- try {
50
- const filename = decodeURIComponent(req.params.filename);
51
- const sourceFilename =
52
- typeof req.query.sourceFilename === "string"
53
- ? req.query.sourceFilename
54
- : decodeHeader(req.headers["x-ima2-canvas-source-filename"]);
55
- const item = await updateCanvasVersion(ctx, filename, {
56
- sourceFilename,
57
- prompt: getPrompt(req),
58
- buffer: getRequestBuffer(req),
59
- });
60
- res.json({ item });
61
- } catch (e) {
62
- const err = errInfo(e);
63
- res.status(err.status || 500).json({
64
- error: err.message,
65
- code: err.code || "CANVAS_VERSION_SAVE_FAILED",
66
- });
67
- }
68
- });
69
- }
@@ -1,18 +0,0 @@
1
- import type { Express, Request, Response } from "express";
2
- import { buildIma2Capabilities } from "../lib/capabilities.js";
3
- import { requireRuntimeContext, type RouteRuntimeContext } from "../lib/runtimeContext.js";
4
-
5
- export function registerCapabilitiesRoutes(app: Express, ctxRaw: RouteRuntimeContext) {
6
- const ctx = requireRuntimeContext(ctxRaw);
7
-
8
- app.get("/api/capabilities", (_req: Request, res: Response) => {
9
- res.json(
10
- buildIma2Capabilities({
11
- appConfig: ctx.config,
12
- packageVersion: ctx.packageVersion,
13
- source: "server",
14
- server: ctx.serverUrl || `http://localhost:${ctx.serverActualPort || ctx.config.server.port}`,
15
- }),
16
- );
17
- });
18
- }