ima2-gen 1.1.20 → 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.
- package/README.md +42 -26
- package/bin/commands/capabilities.js +2 -2
- package/bin/commands/defaults.js +2 -2
- package/bin/commands/doctor.js +3 -3
- package/bin/commands/edit.js +1 -1
- package/bin/commands/gen.js +1 -1
- package/bin/commands/grok.js +16 -11
- package/bin/commands/multimode.js +1 -1
- package/bin/commands/observability.js +2 -2
- package/bin/commands/video.js +335 -13
- package/bin/ima2.js +23 -13
- package/bin/lib/error-hints.js +2 -2
- package/bin/lib/platform.js +34 -5
- package/docs/API.md +112 -3
- package/docs/CLI.md +61 -7
- package/docs/FAQ.ko.md +15 -20
- package/docs/FAQ.md +14 -19
- package/docs/NPX_QUICKSTART.md +40 -0
- package/docs/PROMPT_STUDIO.ko.md +1 -1
- package/docs/PROMPT_STUDIO.md +1 -1
- package/docs/README.ja.md +6 -16
- package/docs/README.ko.md +35 -14
- package/docs/README.zh-CN.md +7 -17
- package/docs/migration/runtime-test-inventory.md +8 -1
- package/lib/agentQueueWorker.js +6 -0
- package/lib/agentRuntime.js +20 -5
- package/lib/atomicWrite.js +14 -0
- package/lib/capabilities.js +1 -1
- package/lib/generationErrors.js +1 -1
- package/lib/grokProxyLauncher.js +31 -6
- package/lib/grokVideoAdapter.js +18 -89
- package/lib/grokVideoCanvas.js +25 -0
- package/lib/grokVideoDownload.js +58 -0
- package/lib/grokVideoPlannerPrompt.js +64 -0
- package/lib/historyList.js +7 -1
- package/lib/inflight.js +1 -1
- package/lib/oauthLauncher.js +26 -6
- package/lib/videoContinuity.js +149 -0
- package/lib/videoFrameExtract.js +80 -0
- package/node_modules/progrok/dist/index.js +187 -88
- package/node_modules/progrok/dist/index.js.map +1 -1
- package/node_modules/progrok/package.json +1 -1
- package/node_modules/progrok/skills/progrok/SKILL.md +33 -4
- package/package.json +6 -8
- package/routes/edit.js +2 -1
- package/routes/generate.js +4 -3
- package/routes/health.js +4 -3
- package/routes/index.js +4 -0
- package/routes/multimode.js +2 -1
- package/routes/quota.js +66 -0
- package/routes/video.js +80 -16
- package/routes/videoExtended.js +293 -0
- package/server.js +35 -4
- package/skills/ima2/SKILL.md +320 -7
- package/ui/dist/.vite/manifest.json +12 -12
- package/ui/dist/assets/{AgentWorkspace-DS8uvoLI.js → AgentWorkspace-COxQ5TjU.js} +2 -2
- package/ui/dist/assets/{CardNewsWorkspace-CYxMsE67.js → CardNewsWorkspace-B0OkcuVz.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-DccIc347.js → NodeCanvas-BSsclEBh.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-BvxxwSJp.js → PromptBuilderPanel-DpC9A5Rz.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-u1_BFDRd.js → PromptImportDialog-CVwT0rLd.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-C5uvkVSz.js → PromptImportDiscoverySection-BDCkRCRs.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-D3E_O1SD.js → PromptImportFolderSection-QoKbZD83.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-4gyf9CB9.js → PromptLibraryPanel-BhFgeKnY.js} +2 -2
- package/ui/dist/assets/SettingsWorkspace-CfjrlH5R.js +1 -0
- package/ui/dist/assets/index-C-mur7pa.css +1 -0
- package/ui/dist/assets/index-CCP5nUOj.js +42 -0
- package/ui/dist/assets/{index-DoKtXbod.js → index-Cxhzi3bs.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/vendor/progrok-0.2.0.tgz +0 -0
- package/bin/commands/annotate.ts +0 -119
- package/bin/commands/cancel.ts +0 -48
- package/bin/commands/canvas-versions.ts +0 -80
- package/bin/commands/capabilities.ts +0 -110
- package/bin/commands/cardnews.ts +0 -249
- package/bin/commands/comfy.ts +0 -54
- package/bin/commands/config.ts +0 -186
- package/bin/commands/defaults.ts +0 -192
- package/bin/commands/doctor.ts +0 -202
- package/bin/commands/edit.ts +0 -150
- package/bin/commands/gen.ts +0 -214
- package/bin/commands/grok.ts +0 -85
- package/bin/commands/history.ts +0 -146
- package/bin/commands/ls.ts +0 -64
- package/bin/commands/metadata.ts +0 -39
- package/bin/commands/multimode.ts +0 -196
- package/bin/commands/node.ts +0 -166
- package/bin/commands/observability.ts +0 -176
- package/bin/commands/ping.ts +0 -31
- package/bin/commands/prompt-sub/build.ts +0 -101
- package/bin/commands/prompt.ts +0 -492
- package/bin/commands/ps.ts +0 -81
- package/bin/commands/session.ts +0 -266
- package/bin/commands/show.ts +0 -72
- package/bin/commands/skill.ts +0 -70
- package/bin/commands/video.ts +0 -205
- package/bin/ima2.ts +0 -430
- package/bin/lib/args.ts +0 -92
- package/bin/lib/browser-id.ts +0 -16
- package/bin/lib/client.ts +0 -122
- package/bin/lib/config-store.ts +0 -120
- package/bin/lib/destructive-confirm.ts +0 -19
- package/bin/lib/doctor-checks.ts +0 -91
- package/bin/lib/error-hints.ts +0 -23
- package/bin/lib/files.ts +0 -39
- package/bin/lib/output.ts +0 -73
- package/bin/lib/platform.ts +0 -99
- package/bin/lib/recover-output.ts +0 -139
- package/bin/lib/sse.ts +0 -73
- package/bin/lib/star-prompt.ts +0 -97
- package/bin/lib/storage-doctor.ts +0 -39
- package/bin/lib/ui-build.ts +0 -85
- package/config.ts +0 -354
- package/lib/agentCommandParser.ts +0 -69
- package/lib/agentGenerationPlanner.ts +0 -273
- package/lib/agentQuestionResponder.ts +0 -266
- package/lib/agentQueueStore.ts +0 -270
- package/lib/agentQueueWorker.ts +0 -89
- package/lib/agentRuntime.ts +0 -592
- package/lib/agentSettings.ts +0 -72
- package/lib/agentStore.ts +0 -422
- package/lib/agentStoreRows.ts +0 -136
- package/lib/agentTypes.ts +0 -154
- package/lib/apiCachePolicy.ts +0 -11
- package/lib/assetLifecycle.ts +0 -146
- package/lib/canvasVersionStore.ts +0 -223
- package/lib/capabilities.ts +0 -126
- package/lib/cardNewsGenerator.ts +0 -271
- package/lib/cardNewsJobStore.ts +0 -142
- package/lib/cardNewsManifestStore.ts +0 -154
- package/lib/cardNewsPlanner.ts +0 -236
- package/lib/cardNewsPlannerClient.ts +0 -155
- package/lib/cardNewsPlannerPrompt.ts +0 -62
- package/lib/cardNewsPlannerSchema.ts +0 -321
- package/lib/cardNewsRoleTemplateStore.ts +0 -47
- package/lib/cardNewsTemplateStore.ts +0 -252
- package/lib/codexDetect.ts +0 -71
- package/lib/comfyBridge.ts +0 -235
- package/lib/composerSnapshot.ts +0 -33
- package/lib/configKeys.ts +0 -62
- package/lib/db.ts +0 -295
- package/lib/errInfo.ts +0 -43
- package/lib/errorClassify.ts +0 -100
- package/lib/generationCancel.ts +0 -28
- package/lib/generationErrors.ts +0 -238
- package/lib/grokImageAdapter.ts +0 -513
- package/lib/grokMultimodeAdapter.ts +0 -84
- package/lib/grokProxyLauncher.ts +0 -129
- package/lib/grokRuntime.ts +0 -23
- package/lib/grokSizeMapper.ts +0 -71
- package/lib/grokVideoAdapter.ts +0 -519
- package/lib/historyIndex.ts +0 -51
- package/lib/historyList.ts +0 -177
- package/lib/imageMetadata.ts +0 -113
- package/lib/imageMetadataStore.ts +0 -67
- package/lib/imageModels.ts +0 -165
- package/lib/inflight.ts +0 -281
- package/lib/localImportStore.ts +0 -114
- package/lib/logger.ts +0 -161
- package/lib/nodeStore.ts +0 -91
- package/lib/oauthLauncher.ts +0 -78
- package/lib/oauthNormalize.ts +0 -30
- package/lib/oauthProxy/errors.ts +0 -128
- package/lib/oauthProxy/generators.ts +0 -494
- package/lib/oauthProxy/index.ts +0 -28
- package/lib/oauthProxy/prompts.ts +0 -123
- package/lib/oauthProxy/references.ts +0 -45
- package/lib/oauthProxy/runtime.ts +0 -115
- package/lib/oauthProxy/streams.ts +0 -232
- package/lib/oauthProxy/types.ts +0 -9
- package/lib/oauthProxy.ts +0 -3
- package/lib/openDirectory.ts +0 -47
- package/lib/pngInfo.ts +0 -26
- package/lib/promptBuilder/attachments.ts +0 -74
- package/lib/promptBuilder/client.ts +0 -130
- package/lib/promptBuilder/constants.ts +0 -9
- package/lib/promptBuilder/context.ts +0 -36
- package/lib/promptBuilder/errors.ts +0 -12
- package/lib/promptBuilder/requestSchema.ts +0 -56
- package/lib/promptBuilder/responseParser.ts +0 -219
- package/lib/promptBuilder/systemPrompt.ts +0 -135
- package/lib/promptBuilder/transport.ts +0 -94
- package/lib/promptBuilder/types.ts +0 -109
- package/lib/promptImport/curatedSources.ts +0 -141
- package/lib/promptImport/discoveryRegistry.ts +0 -329
- package/lib/promptImport/errors.ts +0 -18
- package/lib/promptImport/githubDiscovery.ts +0 -309
- package/lib/promptImport/githubFolder.ts +0 -397
- package/lib/promptImport/githubSource.ts +0 -257
- package/lib/promptImport/gptImageHints.ts +0 -70
- package/lib/promptImport/parsePromptCandidates.ts +0 -179
- package/lib/promptImport/promptIndex.ts +0 -326
- package/lib/promptImport/rankPromptCandidates.ts +0 -65
- package/lib/promptImport/types.ts +0 -103
- package/lib/promptSafetyPolicy.ts +0 -5
- package/lib/providerOptions.ts +0 -56
- package/lib/referenceImageCompress.ts +0 -84
- package/lib/refs.ts +0 -133
- package/lib/requestLogger.ts +0 -49
- package/lib/responsesDoctor.ts +0 -456
- package/lib/responsesErrors.ts +0 -83
- package/lib/responsesFallback.ts +0 -114
- package/lib/responsesImageAdapter.ts +0 -466
- package/lib/responsesParse.ts +0 -452
- package/lib/responsesTools.ts +0 -28
- package/lib/runtimeContext.ts +0 -146
- package/lib/runtimePorts.ts +0 -105
- package/lib/sessionStore.ts +0 -308
- package/lib/storageMigration.ts +0 -310
- package/lib/styleSheet.ts +0 -139
- package/lib/systemTrash.ts +0 -20
- package/lib/videoSeriesChain.ts +0 -29
- package/lib/visibleTextLanguagePolicy.ts +0 -7
- package/routes/agent.ts +0 -308
- package/routes/annotations.ts +0 -118
- package/routes/canvasVersions.ts +0 -69
- package/routes/capabilities.ts +0 -18
- package/routes/cardNews.ts +0 -211
- package/routes/comfy.ts +0 -43
- package/routes/edit.ts +0 -352
- package/routes/generate.ts +0 -492
- package/routes/grok.ts +0 -24
- package/routes/health.ts +0 -123
- package/routes/history.ts +0 -221
- package/routes/imageImport.ts +0 -37
- package/routes/index.ts +0 -48
- package/routes/metadata.ts +0 -77
- package/routes/multimode.ts +0 -499
- package/routes/nodes.ts +0 -578
- package/routes/promptBuilder.ts +0 -37
- package/routes/promptImport.ts +0 -379
- package/routes/prompts.ts +0 -428
- package/routes/sessions.ts +0 -317
- package/routes/storage.ts +0 -47
- package/routes/video.ts +0 -232
- package/server.ts +0 -290
- package/ui/dist/assets/SettingsWorkspace-F3eNu3mJ.js +0 -1
- package/ui/dist/assets/index-B6tcw_UF.css +0 -1
- package/ui/dist/assets/index-DYOh6gQD.js +0 -32
- package/vendor/progrok-0.1.1.tgz +0 -0
|
@@ -95,19 +95,48 @@ curl http://127.0.0.1:18645/v1/images/edits \
|
|
|
95
95
|
### Pattern 4: Video generation (async — poll)
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
#
|
|
98
|
+
# Text-to-video
|
|
99
|
+
progrok video "Ocean waves crashing on rocks" --duration 8 --resolution 720p
|
|
100
|
+
|
|
101
|
+
# Image-to-video
|
|
102
|
+
progrok video "Animate this scene" --image photo.jpg --duration 10
|
|
103
|
+
|
|
104
|
+
# Video editing (real V2V — modify existing video, keep motion)
|
|
105
|
+
progrok video edit "Make the water glow neon blue" --video https://vidgen.x.ai/.../clip.mp4
|
|
106
|
+
|
|
107
|
+
# Video extension (continue from last frame)
|
|
108
|
+
progrok video extend "Camera slowly pulls back" --video https://vidgen.x.ai/.../clip.mp4 --duration 5
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**API endpoints** (via proxy at 127.0.0.1:18645):
|
|
112
|
+
```bash
|
|
113
|
+
# Generate: POST /v1/videos/generations
|
|
99
114
|
curl -s http://127.0.0.1:18645/v1/videos/generations \
|
|
100
115
|
-H "Content-Type: application/json" \
|
|
101
116
|
-d '{"model": "grok-imagine-video", "prompt": "Ocean waves", "duration": 8, "resolution": "720p"}'
|
|
102
117
|
# → {"request_id": "abc-123"}
|
|
103
118
|
|
|
104
|
-
#
|
|
119
|
+
# Edit (V2V): POST /v1/videos/edits — grok-imagine-video only
|
|
120
|
+
curl -s http://127.0.0.1:18645/v1/videos/edits \
|
|
121
|
+
-H "Content-Type: application/json" \
|
|
122
|
+
-d '{"model": "grok-imagine-video", "prompt": "Add sunset colors", "video": {"url": "https://..."}}'
|
|
123
|
+
|
|
124
|
+
# Extend: POST /v1/videos/extensions — grok-imagine-video only, 2-10s
|
|
125
|
+
curl -s http://127.0.0.1:18645/v1/videos/extensions \
|
|
126
|
+
-H "Content-Type: application/json" \
|
|
127
|
+
-d '{"model": "grok-imagine-video", "prompt": "Continue scene", "duration": 5, "video": {"url": "https://..."}}'
|
|
128
|
+
|
|
129
|
+
# Poll: GET /v1/videos/{request_id}
|
|
105
130
|
curl http://127.0.0.1:18645/v1/videos/abc-123
|
|
106
|
-
# → {"status": "pending", "progress": 45}
|
|
107
131
|
# → {"status": "done", "video": {"url": "https://...", "duration": 8}}
|
|
108
132
|
```
|
|
109
133
|
|
|
110
|
-
|
|
134
|
+
**Model constraints:**
|
|
135
|
+
- `grok-imagine-video`: T2V, I2V, Ref2V, Edit, Extend — all modes
|
|
136
|
+
- `grok-imagine-video-1.5-preview`: I2V, Ref2V only (no T2V, no Edit, no Extend)
|
|
137
|
+
- Edit/Extend input: mp4, H.264/H.265/AV1, max 8.7s (edit) / 2-15s (extend)
|
|
138
|
+
- Edit output inherits input duration/aspect/resolution (max 720p)
|
|
139
|
+
- Extend duration: 2-10s added to original
|
|
111
140
|
|
|
112
141
|
### Pattern 5: Voice — TTS / STT (HTTP)
|
|
113
142
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima2-gen",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.22",
|
|
4
4
|
"description": "Local OAuth image generation studio with classic and node workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"setup": "node bin/ima2.js setup",
|
|
21
21
|
"prepack": "npm run ui:build && npm run build:server && npm run build:cli",
|
|
22
22
|
"prepublishOnly": "npm run typecheck && npm run typecheck:tests && npm run test:inventory && npm run ui:build && npm run build:server && npm run build:cli && npm run lint:pkg && npm run test:package-install",
|
|
23
|
-
"lint:pkg": "node -e \"const p=require('./package.json'); const req=['name','version','bin']; for(const k of req){if(!p[k])throw new Error('missing '+k)} const mustInclude=['bin
|
|
23
|
+
"lint:pkg": "node -e \"const p=require('./package.json'); const req=['name','version','bin']; for(const k of req){if(!p[k])throw new Error('missing '+k)} const mustInclude=['bin/**/*.js','lib/**/*.js','routes/**/*.js','skills/','integrations/comfyui/ima2_gen_bridge/__init__.py','integrations/comfyui/ima2_gen_bridge/nodes.py','integrations/comfyui/ima2_gen_bridge/README.md','assets/card-news/templates/','vendor/','server.js','LICENSE']; for(const f of mustInclude){if(!p.files.includes(f))throw new Error('files[] must include '+f)}\"",
|
|
24
24
|
"publish:dry-run": "node scripts/publish-dry-run.mjs",
|
|
25
25
|
"release:patch": "npm version patch && npm publish && git push origin main --tags",
|
|
26
26
|
"release:minor": "npm version minor && npm publish && git push origin main --tags",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"url": "git+https://github.com/lidge-jun/ima2-gen.git"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
|
-
"bin
|
|
47
|
-
"lib
|
|
48
|
-
"routes
|
|
46
|
+
"bin/**/*.js",
|
|
47
|
+
"lib/**/*.js",
|
|
48
|
+
"routes/**/*.js",
|
|
49
49
|
"skills/",
|
|
50
50
|
"integrations/comfyui/ima2_gen_bridge/__init__.py",
|
|
51
51
|
"integrations/comfyui/ima2_gen_bridge/nodes.py",
|
|
@@ -54,8 +54,6 @@
|
|
|
54
54
|
"docs/",
|
|
55
55
|
"vendor/",
|
|
56
56
|
"assets/card-news/templates/",
|
|
57
|
-
"server.ts",
|
|
58
|
-
"config.ts",
|
|
59
57
|
".env.example",
|
|
60
58
|
"README.md",
|
|
61
59
|
"LICENSE",
|
|
@@ -71,7 +69,7 @@
|
|
|
71
69
|
"express": "^5.1.0",
|
|
72
70
|
"openai": "^5.8.2",
|
|
73
71
|
"openai-oauth": "^1.0.2",
|
|
74
|
-
"progrok": "file:vendor/progrok-0.
|
|
72
|
+
"progrok": "file:vendor/progrok-0.2.0.tgz",
|
|
75
73
|
"sharp": "^0.34.5",
|
|
76
74
|
"trash": "^10.1.1",
|
|
77
75
|
"ulid": "^3.0.2"
|
package/routes/edit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "fs/promises";
|
|
2
|
+
import { safeWriteSidecar } from "../lib/atomicWrite.js";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { randomBytes } from "crypto";
|
|
4
5
|
import { detectImageMimeFromB64 } from "../lib/refs.js";
|
|
@@ -222,7 +223,7 @@ export function registerEditRoutes(app, ctxRaw) {
|
|
|
222
223
|
webSearchCalls,
|
|
223
224
|
webSearchEnabled,
|
|
224
225
|
};
|
|
225
|
-
await
|
|
226
|
+
await safeWriteSidecar(join(ctx.config.storage.generatedDir, filename + ".json"), meta);
|
|
226
227
|
invalidateHistoryIndex();
|
|
227
228
|
finishHttpStatus = 200;
|
|
228
229
|
finishMeta = { filename, imageChars: resultB64.length };
|
package/routes/generate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { safeWriteSidecar, atomicWriteJson } from "../lib/atomicWrite.js";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { randomBytes } from "crypto";
|
|
4
5
|
import { detectImageMimeFromB64, summarizeReferencePayload, validateAndNormalizeRefs } from "../lib/refs.js";
|
|
@@ -260,7 +261,7 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
260
261
|
});
|
|
261
262
|
}
|
|
262
263
|
await writeFile(join(ctx.config.storage.generatedDir, filename), embedded.buffer);
|
|
263
|
-
await
|
|
264
|
+
await safeWriteSidecar(join(ctx.config.storage.generatedDir, filename + ".json"), meta);
|
|
264
265
|
invalidateHistoryIndex();
|
|
265
266
|
images.push({
|
|
266
267
|
image: `data:${resultMime};base64,${r.value.b64}`,
|
|
@@ -346,10 +347,10 @@ export function registerGenerateRoutes(app, ctxRaw) {
|
|
|
346
347
|
const sidecarPath = join(ctx.config.storage.generatedDir, filename + ".json");
|
|
347
348
|
const sidecarMeta = JSON.parse(await readFile(sidecarPath, "utf-8"));
|
|
348
349
|
sidecarMeta.elapsed = elapsed;
|
|
349
|
-
await
|
|
350
|
+
await atomicWriteJson(sidecarPath, sidecarMeta);
|
|
350
351
|
}
|
|
351
352
|
catch {
|
|
352
|
-
/* best-effort
|
|
353
|
+
/* best-effort elapsed patch */
|
|
353
354
|
}
|
|
354
355
|
}));
|
|
355
356
|
const firstRevised = images[0]?.revisedPrompt || null;
|
package/routes/health.js
CHANGED
|
@@ -95,10 +95,11 @@ export function registerHealthRoutes(app, ctxRaw) {
|
|
|
95
95
|
const headers = { Authorization: `Bearer ${ctx.apiKey}`, "Content-Type": "application/json" };
|
|
96
96
|
const start = Math.floor(new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime() / 1000);
|
|
97
97
|
const end = Math.floor(Date.now() / 1000);
|
|
98
|
+
const billingSignal = AbortSignal.timeout(10_000);
|
|
98
99
|
const [subRes, usageRes, modelsRes] = await Promise.allSettled([
|
|
99
|
-
fetch(`https://api.openai.com/v1/organization/costs?start_time=${start}&end_time=${end}&bucket_width=1d&limit=31`, { headers }),
|
|
100
|
-
fetch("https://api.openai.com/dashboard/billing/credit_grants", { headers }),
|
|
101
|
-
fetch("https://api.openai.com/v1/models", { headers }),
|
|
100
|
+
fetch(`https://api.openai.com/v1/organization/costs?start_time=${start}&end_time=${end}&bucket_width=1d&limit=31`, { headers, signal: billingSignal }),
|
|
101
|
+
fetch("https://api.openai.com/dashboard/billing/credit_grants", { headers, signal: billingSignal }),
|
|
102
|
+
fetch("https://api.openai.com/v1/models", { headers, signal: billingSignal }),
|
|
102
103
|
]);
|
|
103
104
|
const billing = { apiKeySource: ctx.apiKeySource ?? "env" };
|
|
104
105
|
if (subRes.status === "fulfilled" && subRes.value.ok)
|
package/routes/index.js
CHANGED
|
@@ -19,6 +19,8 @@ import { registerPromptBuilderRoutes } from "./promptBuilder.js";
|
|
|
19
19
|
import { registerAgentRoutes } from "./agent.js";
|
|
20
20
|
import { registerGrokRoutes } from "./grok.js";
|
|
21
21
|
import { registerVideoRoutes } from "./video.js";
|
|
22
|
+
import { registerVideoExtendedRoutes } from "./videoExtended.js";
|
|
23
|
+
import { registerQuotaRoutes } from "./quota.js";
|
|
22
24
|
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
23
25
|
export function configureRoutes(app, ctxRaw) {
|
|
24
26
|
const ctx = requireRuntimeContext(ctxRaw);
|
|
@@ -44,4 +46,6 @@ export function configureRoutes(app, ctxRaw) {
|
|
|
44
46
|
registerPromptImportRoutes(app, ctx);
|
|
45
47
|
registerGrokRoutes(app, ctx);
|
|
46
48
|
registerVideoRoutes(app, ctx);
|
|
49
|
+
registerVideoExtendedRoutes(app, ctx);
|
|
50
|
+
registerQuotaRoutes(app, ctx);
|
|
47
51
|
}
|
package/routes/multimode.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "fs/promises";
|
|
2
|
+
import { safeWriteSidecar } from "../lib/atomicWrite.js";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { randomBytes } from "crypto";
|
|
4
5
|
import { detectImageMimeFromB64, summarizeReferencePayload, validateAndNormalizeRefs } from "../lib/refs.js";
|
|
@@ -217,7 +218,7 @@ export function registerMultimodeRoutes(app, ctxRaw) {
|
|
|
217
218
|
version: ctx.packageVersion,
|
|
218
219
|
});
|
|
219
220
|
await writeFile(join(ctx.config.storage.generatedDir, filename), embedded.buffer);
|
|
220
|
-
await
|
|
221
|
+
await safeWriteSidecar(join(ctx.config.storage.generatedDir, filename + ".json"), meta);
|
|
221
222
|
invalidateHistoryIndex();
|
|
222
223
|
const item = {
|
|
223
224
|
image: `data:${resultMime};base64,${image.b64}`,
|
package/routes/quota.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
function readCodexTokens() {
|
|
5
|
+
const codexHome = process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
6
|
+
try {
|
|
7
|
+
const j = JSON.parse(readFileSync(join(codexHome, "auth.json"), "utf8"));
|
|
8
|
+
if (j?.tokens?.access_token) {
|
|
9
|
+
return { access_token: j.tokens.access_token, account_id: j.tokens.account_id ?? "" };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
catch { }
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
async function fetchCodexUsage(tokens) {
|
|
16
|
+
try {
|
|
17
|
+
const resp = await fetch("https://chatgpt.com/backend-api/wham/usage", {
|
|
18
|
+
headers: {
|
|
19
|
+
Authorization: `Bearer ${tokens.access_token}`,
|
|
20
|
+
"ChatGPT-Account-Id": tokens.account_id,
|
|
21
|
+
},
|
|
22
|
+
signal: AbortSignal.timeout(8000),
|
|
23
|
+
});
|
|
24
|
+
if (!resp.ok) {
|
|
25
|
+
if (resp.status === 401 || resp.status === 403)
|
|
26
|
+
return { provider: "codex", authenticated: false, windows: [] };
|
|
27
|
+
return { provider: "codex", error: true, windows: [] };
|
|
28
|
+
}
|
|
29
|
+
const data = await resp.json();
|
|
30
|
+
const account = { email: data.email ?? null, plan: data.plan_type ?? null };
|
|
31
|
+
const windows = [];
|
|
32
|
+
if (data.rate_limit?.primary_window) {
|
|
33
|
+
windows.push({
|
|
34
|
+
label: "5h",
|
|
35
|
+
percent: Math.round(data.rate_limit.primary_window.used_percent ?? 0),
|
|
36
|
+
resetsAt: data.rate_limit.primary_window.reset_at
|
|
37
|
+
? new Date(data.rate_limit.primary_window.reset_at * 1000).toISOString()
|
|
38
|
+
: null,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (data.rate_limit?.secondary_window) {
|
|
42
|
+
windows.push({
|
|
43
|
+
label: "7d",
|
|
44
|
+
percent: Math.round(data.rate_limit.secondary_window.used_percent ?? 0),
|
|
45
|
+
resetsAt: data.rate_limit.secondary_window.reset_at
|
|
46
|
+
? new Date(data.rate_limit.secondary_window.reset_at * 1000).toISOString()
|
|
47
|
+
: null,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { provider: "codex", account, windows };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return { provider: "codex", error: true, windows: [] };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function registerQuotaRoutes(app, _ctx) {
|
|
57
|
+
app.get("/api/quota", async (_req, res) => {
|
|
58
|
+
const tokens = readCodexTokens();
|
|
59
|
+
if (!tokens) {
|
|
60
|
+
res.json({ codex: { provider: "codex", authenticated: false, windows: [] } });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const codex = await fetchCodexUsage(tokens);
|
|
64
|
+
res.json({ codex });
|
|
65
|
+
});
|
|
66
|
+
}
|
package/routes/video.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
1
|
+
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
2
|
+
import { atomicWriteJson } from "../lib/atomicWrite.js";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { randomBytes } from "crypto";
|
|
4
5
|
import { startJob, finishJob, registerJobAbortController, isJobCanceled, setJobPhase } from "../lib/inflight.js";
|
|
@@ -7,6 +8,8 @@ import { logEvent, logError } from "../lib/logger.js";
|
|
|
7
8
|
import { invalidateHistoryIndex } from "../lib/historyIndex.js";
|
|
8
9
|
import { generateVideoViaGrok } from "../lib/grokVideoAdapter.js";
|
|
9
10
|
import { getVideoSeriesChain } from "../lib/videoSeriesChain.js";
|
|
11
|
+
import { ACTIVE_VIDEO_PROMPT_GUIDANCE, appendVideoContinuityEntry, lineageFromVideoMetadata, normalizeVideoContinuityLineage, readVideoSidecar, requireActiveVideoPrompt, safeGeneratedVideoFilename, } from "../lib/videoContinuity.js";
|
|
12
|
+
import { extractGeneratedVideoFrameB64 } from "../lib/videoFrameExtract.js";
|
|
10
13
|
import { normalizeGrokVideoModel, normalizeVideoResolution, normalizeVideoAspectRatio, normalizeVideoDuration, deriveVideoMode, clampVideoDuration, MAX_REF2V_REFERENCES, } from "../lib/imageModels.js";
|
|
11
14
|
import { errInfo } from "../lib/errInfo.js";
|
|
12
15
|
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
@@ -20,11 +23,25 @@ function toArray(v) {
|
|
|
20
23
|
function isNormalizeError(x) {
|
|
21
24
|
return typeof x === "object" && x !== null && typeof x.error === "string";
|
|
22
25
|
}
|
|
26
|
+
export async function saveGeneratedVideoArtifact(ctx, filename, buffer, metadata) {
|
|
27
|
+
const filePath = join(ctx.config.storage.generatedDir, filename);
|
|
28
|
+
await writeFile(filePath, buffer);
|
|
29
|
+
try {
|
|
30
|
+
await atomicWriteJson(`${filePath}.json`, metadata);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
await unlink(filePath).catch(() => { });
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
23
37
|
async function resolveSourceImage(ctx, sourceImage, sourceFilename) {
|
|
24
38
|
if (typeof sourceFilename === "string" && sourceFilename) {
|
|
25
39
|
const safe = sourceFilename.replace(/^\/+/, "");
|
|
26
|
-
if (safe.includes(".."))
|
|
40
|
+
if (safe.includes("..") || safe.includes("/") || safe.includes("\\")) {
|
|
27
41
|
throw { status: 400, code: "GROK_VIDEO_INVALID_MODE", message: "invalid source filename" };
|
|
42
|
+
}
|
|
43
|
+
if (/\.mp4$/i.test(safe))
|
|
44
|
+
throw { status: 400, code: "GROK_VIDEO_INVALID_MODE", message: "use continueFromVideo for generated video continuation" };
|
|
28
45
|
const buf = await readFile(join(ctx.config.storage.generatedDir, safe));
|
|
29
46
|
return { b64: buf.toString("base64"), filename: safe };
|
|
30
47
|
}
|
|
@@ -51,12 +68,12 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
51
68
|
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
52
69
|
res.setHeader("Connection", "keep-alive");
|
|
53
70
|
res.flushHeaders?.();
|
|
54
|
-
const fail = (status, code, error) => {
|
|
71
|
+
const fail = (status, code, error, extra = {}) => {
|
|
55
72
|
const httpStatus = status ?? 500;
|
|
56
73
|
finishStatus = "error";
|
|
57
74
|
finishHttpStatus = httpStatus;
|
|
58
75
|
finishErrorCode = code;
|
|
59
|
-
sendSse(res, "error", { error, code, status: httpStatus, requestId });
|
|
76
|
+
sendSse(res, "error", { error, code, status: httpStatus, requestId, ...extra });
|
|
60
77
|
};
|
|
61
78
|
try {
|
|
62
79
|
const { prompt, provider = "grok", model: rawModel } = req.body || {};
|
|
@@ -65,8 +82,9 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
65
82
|
const topic = typeof req.body?.topic === "string" ? req.body.topic.trim() : "";
|
|
66
83
|
if (provider !== "grok")
|
|
67
84
|
return fail(400, "VIDEO_PROVIDER_UNSUPPORTED", "video generation requires provider 'grok'");
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
const activePrompt = requireActiveVideoPrompt(prompt);
|
|
86
|
+
if (!activePrompt)
|
|
87
|
+
return fail(400, "PROMPT_REQUIRED", "Prompt is required", { guidance: ACTIVE_VIDEO_PROMPT_GUIDANCE });
|
|
70
88
|
const modelCheck = normalizeGrokVideoModel(rawModel);
|
|
71
89
|
if (isNormalizeError(modelCheck))
|
|
72
90
|
return fail(modelCheck.status, modelCheck.code, modelCheck.error);
|
|
@@ -80,6 +98,21 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
80
98
|
if (isNormalizeError(aspectCheck))
|
|
81
99
|
return fail(aspectCheck.status, aspectCheck.code, aspectCheck.error);
|
|
82
100
|
// Resolve reference inputs: base64 list + existing-file list + legacy single source.
|
|
101
|
+
let parentLineage = null;
|
|
102
|
+
let continueFromVideoFilename = null;
|
|
103
|
+
if (typeof req.body?.continueFromVideo === "string" && req.body.continueFromVideo.trim()) {
|
|
104
|
+
try {
|
|
105
|
+
continueFromVideoFilename = safeGeneratedVideoFilename(req.body.continueFromVideo);
|
|
106
|
+
const parentMeta = await readVideoSidecar(ctx.config.storage.generatedDir, continueFromVideoFilename);
|
|
107
|
+
parentLineage = lineageFromVideoMetadata(continueFromVideoFilename, parentMeta);
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return fail(e?.status || 400, "GROK_VIDEO_INVALID_MODE", e?.message || "invalid continuation video");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
parentLineage = normalizeVideoContinuityLineage(req.body?.continuityLineage);
|
|
115
|
+
}
|
|
83
116
|
const refInputs = [
|
|
84
117
|
...toArray(req.body?.referenceImages).map((image) => ({ image })),
|
|
85
118
|
...toArray(req.body?.referenceFilenames).map((filename) => ({ filename })),
|
|
@@ -87,6 +120,14 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
87
120
|
? [{ image: req.body?.sourceImage, filename: req.body?.sourceFilename }]
|
|
88
121
|
: []),
|
|
89
122
|
];
|
|
123
|
+
if (continueFromVideoFilename && !req.body?.sourceImage && !req.body?.sourceFilename) {
|
|
124
|
+
try {
|
|
125
|
+
refInputs.push({ image: await extractGeneratedVideoFrameB64(ctx.config.storage.generatedDir, continueFromVideoFilename) });
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
return fail(e?.status || 500, "GROK_VIDEO_FRAME_FAILED", e?.message || "failed to extract continuation frame");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
90
131
|
let resolved;
|
|
91
132
|
try {
|
|
92
133
|
const all = await Promise.all(refInputs.map((r) => resolveSourceImage(ctx, r.image, r.filename)));
|
|
@@ -105,7 +146,7 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
105
146
|
startJob({
|
|
106
147
|
requestId,
|
|
107
148
|
kind: "video",
|
|
108
|
-
prompt,
|
|
149
|
+
prompt: activePrompt,
|
|
109
150
|
meta: { kind: "video", sessionId, clientNodeId, model: modelCheck.model, mode, duration, resolution: resolutionCheck.resolution },
|
|
110
151
|
});
|
|
111
152
|
registerJobAbortController(requestId, cancelController);
|
|
@@ -115,7 +156,13 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
115
156
|
const onEvent = (ev) => {
|
|
116
157
|
if (ev.phase === "submitted") {
|
|
117
158
|
setJobPhase(requestId, "streaming");
|
|
118
|
-
sendSse(res, "submitted", {
|
|
159
|
+
sendSse(res, "submitted", {
|
|
160
|
+
requestId,
|
|
161
|
+
xaiVideoRequestId: ev.xaiVideoRequestId,
|
|
162
|
+
requestedModel: ev.requestedModel,
|
|
163
|
+
effectiveModel: ev.effectiveModel,
|
|
164
|
+
modelFallback: ev.modelFallback ?? null,
|
|
165
|
+
});
|
|
119
166
|
}
|
|
120
167
|
else if (ev.phase === "progress") {
|
|
121
168
|
sendSse(res, "progress", { requestId, progress: typeof ev.progress === "number" ? ev.progress / 100 : null, stalled: Boolean(ev.stalled) });
|
|
@@ -126,10 +173,10 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
126
173
|
}
|
|
127
174
|
};
|
|
128
175
|
// Build prompt with series chain context
|
|
129
|
-
const chain = topic ? await getVideoSeriesChain(ctx.config.storage.generatedDir, topic) : [];
|
|
176
|
+
const chain = !parentLineage && topic ? await getVideoSeriesChain(ctx.config.storage.generatedDir, topic) : [];
|
|
130
177
|
const effectivePrompt = chain.length > 0
|
|
131
|
-
? `[Series topic: ${topic}]\n[Previous prompts in series:\n${chain.map((p, i) => `${i + 1}. ${p}`).join("\n")}\n]\n\n${
|
|
132
|
-
:
|
|
178
|
+
? `[Series topic: ${topic}]\n[Previous prompts in series:\n${chain.map((p, i) => `${i + 1}. ${p}`).join("\n")}\n]\n\n${activePrompt}`
|
|
179
|
+
: activePrompt;
|
|
133
180
|
const result = await generateVideoViaGrok(effectivePrompt, ctx, {
|
|
134
181
|
model: modelCheck.model,
|
|
135
182
|
mode,
|
|
@@ -140,22 +187,32 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
140
187
|
referenceImages,
|
|
141
188
|
signal: cancelController.signal,
|
|
142
189
|
requestId,
|
|
190
|
+
continuityLineage: parentLineage,
|
|
143
191
|
onEvent,
|
|
144
192
|
});
|
|
145
193
|
const rand = randomBytes(ctx.config.ids.generatedHexBytes).toString("hex");
|
|
146
194
|
const filename = `${Date.now()}_${rand}.mp4`;
|
|
147
195
|
const elapsed = +((Date.now() - startTime) / 1000).toFixed(1);
|
|
196
|
+
const videoContinuity = appendVideoContinuityEntry(parentLineage, {
|
|
197
|
+
filename,
|
|
198
|
+
userPrompt: activePrompt,
|
|
199
|
+
revisedPrompt: result.revisedPrompt,
|
|
200
|
+
createdAt: Date.now(),
|
|
201
|
+
});
|
|
148
202
|
const meta = {
|
|
149
203
|
kind: "video",
|
|
150
204
|
mediaType: "video",
|
|
151
205
|
requestId,
|
|
152
206
|
sessionId,
|
|
153
207
|
clientNodeId,
|
|
154
|
-
prompt,
|
|
155
|
-
userPrompt:
|
|
208
|
+
prompt: activePrompt,
|
|
209
|
+
userPrompt: activePrompt,
|
|
156
210
|
revisedPrompt: result.revisedPrompt,
|
|
157
211
|
provider: "grok",
|
|
158
|
-
model:
|
|
212
|
+
model: result.effectiveModel,
|
|
213
|
+
requestedModel: result.requestedModel,
|
|
214
|
+
effectiveModel: result.effectiveModel,
|
|
215
|
+
modelFallback: result.modelFallback,
|
|
159
216
|
createdAt: Date.now(),
|
|
160
217
|
elapsed,
|
|
161
218
|
usage: result.usage,
|
|
@@ -166,11 +223,14 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
166
223
|
aspectRatio: result.aspectRatio,
|
|
167
224
|
sourceImageFilename: sourceFilename,
|
|
168
225
|
xaiVideoRequestId: result.xaiVideoRequestId,
|
|
226
|
+
requestedModel: result.requestedModel,
|
|
227
|
+
effectiveModel: result.effectiveModel,
|
|
228
|
+
modelFallback: result.modelFallback,
|
|
169
229
|
},
|
|
230
|
+
videoContinuity,
|
|
170
231
|
...(topic ? { videoSeries: { topic, chainIndex: chain.length } } : {}),
|
|
171
232
|
};
|
|
172
|
-
await
|
|
173
|
-
await writeFile(join(ctx.config.storage.generatedDir, filename + ".json"), JSON.stringify(meta)).catch(() => { });
|
|
233
|
+
await saveGeneratedVideoArtifact(ctx, filename, result.videoBuffer, meta);
|
|
174
234
|
invalidateHistoryIndex();
|
|
175
235
|
finishMeta = { filename, xaiVideoRequestId: result.xaiVideoRequestId };
|
|
176
236
|
logEvent("video", "saved", { requestId, filename, bytes: result.videoBuffer.length, elapsedMs: Date.now() - startTime });
|
|
@@ -182,7 +242,11 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
182
242
|
revisedPrompt: result.revisedPrompt,
|
|
183
243
|
elapsed,
|
|
184
244
|
usage: result.usage,
|
|
245
|
+
requestedModel: result.requestedModel,
|
|
246
|
+
effectiveModel: result.effectiveModel,
|
|
247
|
+
modelFallback: result.modelFallback,
|
|
185
248
|
video: meta.video,
|
|
249
|
+
videoContinuity,
|
|
186
250
|
...(meta.videoSeries ? { videoSeries: meta.videoSeries } : {}),
|
|
187
251
|
});
|
|
188
252
|
}
|