ima2-gen 3.15.0 → 3.15.1
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 +1 -1
- package/bin/commands/video.js +6 -4
- package/docs/API.md +1 -1
- package/docs/API.zh-CN.md +1 -1
- package/docs/API.zh-TW.md +1 -1
- package/docs/CLI.md +1 -1
- package/docs/CLI.zh-CN.md +1 -1
- package/docs/CLI.zh-TW.md +1 -1
- package/docs/README.ja.md +1 -1
- package/docs/README.ko.md +2 -2
- package/docs/README.zh-CN.md +1 -1
- package/docs/README.zh-TW.md +1 -1
- package/docs/grok-video-i2v-research.md +1 -1
- package/docs/grok-video-i2v-research.zh-CN.md +1 -1
- package/docs/grok-video-i2v-research.zh-TW.md +1 -1
- package/docs/migration/runtime-test-inventory.md +5 -1
- package/lib/capabilities.js +33 -7
- package/lib/grokVideoAdapter.js +9 -4
- package/lib/imageModels.js +81 -8
- package/lib/providers/registry.js +11 -4
- package/package.json +2 -2
- package/routes/video.js +30 -15
- package/routes/videoExtended.js +8 -3
- package/skills/ima2/SKILL.md +25 -14
- package/ui/dist/.vite/manifest.json +61 -61
- package/ui/dist/assets/{AgentWorkspace-BulKz33A.js → AgentWorkspace-CLJDCRMQ.js} +1 -1
- package/ui/dist/assets/App-DkvU6Sex.js +9 -0
- package/ui/dist/assets/{AssetGenWorkspace-8SYhS2Y2.js → AssetGenWorkspace-BepUrz23.js} +2 -2
- package/ui/dist/assets/{AssetsWorkspace-Dxz-Xksp.js → AssetsWorkspace-IipC1FdD.js} +1 -1
- package/ui/dist/assets/{CardNewsWorkspace-DV4hpvKf.js → CardNewsWorkspace-ZXDwYz4l.js} +1 -1
- package/ui/dist/assets/{GenerationRequestLogPanel-VFu4cAdd.js → GenerationRequestLogPanel-BL2C0DcW.js} +1 -1
- package/ui/dist/assets/{HomeWorkspace-D1UBgwRZ.js → HomeWorkspace-DFfrwS9a.js} +1 -1
- package/ui/dist/assets/{KeyingPanel-GdUcaaJR.js → KeyingPanel-BQkEMTe9.js} +1 -1
- package/ui/dist/assets/{NodeCanvas-B6zoJhHr.js → NodeCanvas-5u8IO1MA.js} +1 -1
- package/ui/dist/assets/{PromptBuilderPanel-CJ56MEk2.js → PromptBuilderPanel-DNt7LHMi.js} +1 -1
- package/ui/dist/assets/{PromptImportDialog-DMFnggIT.js → PromptImportDialog-Bl-SB4mK.js} +2 -2
- package/ui/dist/assets/{PromptImportDiscoverySection-4KCIWmZ8.js → PromptImportDiscoverySection-DdZAdRMW.js} +1 -1
- package/ui/dist/assets/{PromptImportFolderSection-BjqopFX5.js → PromptImportFolderSection-DLHQKl9T.js} +1 -1
- package/ui/dist/assets/{PromptLibraryPanel-DwFK4ALC.js → PromptLibraryPanel-PLWyvEz1.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-BWcNnQ56.js → SettingsWorkspace-Etfeu2IF.js} +1 -1
- package/ui/dist/assets/{SpriteRecipeWorkspace-Bsl_NZ3A.js → SpriteRecipeWorkspace-D7H5seOM.js} +1 -1
- package/ui/dist/assets/{index-BWzT2RyM.js → index-BAkbTbUC.js} +3 -3
- package/ui/dist/assets/index-D0NhOovz.css +1 -0
- package/ui/dist/assets/index-DYtUJ4t0.js +22 -0
- package/ui/dist/assets/{pptxgen.es-BslswdiL.js → pptxgen.es-Apxh5DQ9.js} +1 -1
- package/ui/dist/assets/{promptBuilderStore-Cl2q0Oe6.js → promptBuilderStore-prvdLIUP.js} +1 -1
- package/ui/dist/assets/useAgentDialogFocus-B2snKbzK.js +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/App-juOjrIgh.js +0 -9
- package/ui/dist/assets/index-C5zSMtGM.css +0 -1
- package/ui/dist/assets/index-Cl5SnWLL.js +0 -22
- package/ui/dist/assets/useAgentDialogFocus-B8niAv4W.js +0 -1
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ If no provider is specified, the app keeps the current GPT OAuth/default behavio
|
|
|
182
182
|
|
|
183
183
|
Grok image generation exposes a model picker (`grok-imagine-image` / `grok-imagine-image-quality`) and a size picker (aspect ratio + 1k/2k resolution). The Settings page prefers the Grok Build weekly credits percentage and reset time from `GET /v1/billing?format=credits`; if that source is unavailable, it falls back to the legacy monthly billing window and `$used/$limit`. A **Switch Account** button starts a device-code OAuth flow (`POST /api/auth/switch`) for re-authenticating without leaving the app.
|
|
184
184
|
|
|
185
|
-
Grok video generation defaults to canonical `grok-imagine-video-1.5`; `grok-imagine-video` remains available for base-model-only Ref2V, V2V edit, and extension paths, and the legacy `grok-imagine-video-1.5-preview` string is accepted as an alias. Three modes are auto-detected from reference count: text-to-video (0 refs), image-to-video (1 ref), and reference-to-video (2-
|
|
185
|
+
Grok video generation defaults to canonical `grok-imagine-video-1.5`; `grok-imagine-video` remains available for base-model-only Ref2V, V2V edit, and extension paths, and the legacy `grok-imagine-video-1.5-preview` string is accepted as an alias. Three modes are auto-detected from reference count: text-to-video (0 refs), image-to-video (1 ref), and reference-to-video (2-14 refs; up to 15s on grok-imagine-video-1.5, 10s on grok-imagine-video). 1080p is available for `grok-imagine-video-1.5` prompt-only text-to-video and single image/frame image-to-video; prompt-only 1.5 uses the internal white-canvas I2V shim before the upstream request. Video controls include duration (1-15s), resolution (480p, 720p, 1080p when supported), and aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, auto).
|
|
186
186
|
|
|
187
187
|

|
|
188
188
|
|
package/bin/commands/video.js
CHANGED
|
@@ -5,7 +5,7 @@ import { out, die, color, exitCodeForError, fail } from "../lib/output.js";
|
|
|
5
5
|
import { writeFile, mkdir, readFile, stat } from "node:fs/promises";
|
|
6
6
|
import { basename, dirname } from "node:path";
|
|
7
7
|
import { runVideoGenerate } from "../lib/videoMcp.js";
|
|
8
|
-
import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_15_PREVIEW_ALIAS, GROK_VIDEO_MODEL_BASE, validateVideoResolutionForRequest, } from "../../lib/imageModels.js";
|
|
8
|
+
import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_15_PREVIEW_ALIAS, GROK_VIDEO_MODEL_BASE, MAX_VIDEO_DURATION, MIN_VIDEO_DURATION, validateVideoResolutionForRequest, } from "../../lib/imageModels.js";
|
|
9
9
|
import { VIDEO_CLIENT_TIMEOUT_SEC } from "../../lib/videoClientTimeouts.js";
|
|
10
10
|
import { deriveVideoProviderIds } from "../../lib/providers/derive.js";
|
|
11
11
|
import { listProviders } from "../../lib/mcp/providerRegistry.js";
|
|
@@ -291,7 +291,7 @@ async function videoExtendCmd(argv) {
|
|
|
291
291
|
const args = parseArgs(argv, spec);
|
|
292
292
|
rejectUnknownFlags(args);
|
|
293
293
|
if (args.help) {
|
|
294
|
-
out(` ima2 video extend <prompt> --video <url|file_id|generated-file> [--duration 6]\n\n Extend video from its last frame.\n Model: grok-imagine-video only. Extension:
|
|
294
|
+
out(` ima2 video extend <prompt> --video <url|file_id|generated-file> [--duration 6]\n\n Extend video from its last frame.\n Model: grok-imagine-video only (1.5 rejects extensions). Extension: 1-15s.\n duration is the length of the ADDED segment, not the total output.\n\n Options:\n --video <value> Source video HTTPS URL, xAI file_id, data URL, or generated filename (required)\n --duration <1-15> Added-segment duration (default: 6)\n -o, --out <file> Download extended video to file\n --output <file> Alias for --out\n --json Print JSON result\n --timeout <sec> Default: 5400\n --server <url> Override server URL`);
|
|
295
295
|
return;
|
|
296
296
|
}
|
|
297
297
|
const prompt = args.positional.join(" ");
|
|
@@ -300,8 +300,10 @@ async function videoExtendCmd(argv) {
|
|
|
300
300
|
if (!args.video)
|
|
301
301
|
die(2, "--video <url> is required");
|
|
302
302
|
const duration = parseIntegerFlag(args.duration, 6, "--duration");
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
// Measured 1-15, not the documented 2-10: the endpoint accepts 1 and 11 and refuses
|
|
304
|
+
// only 0 and 16. devlog/_plan/260908_xai_imagine_spec_resync/000_research.md (D11)
|
|
305
|
+
if (duration < MIN_VIDEO_DURATION || duration > MAX_VIDEO_DURATION)
|
|
306
|
+
die(2, `--duration must be between ${MIN_VIDEO_DURATION} and ${MAX_VIDEO_DURATION}`);
|
|
305
307
|
parseTimeoutSeconds(args.timeout);
|
|
306
308
|
const server = await resolveServer({ serverFlag: args.server });
|
|
307
309
|
const res = await fetchServer(server.base, "/api/video/extend", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt, videoUrl: args.video, duration }), signal: timeoutSignal(args.timeout) });
|
package/docs/API.md
CHANGED
|
@@ -520,7 +520,7 @@ Generate a video via the Grok video provider. Returns Server-Sent Events on the
|
|
|
520
520
|
|---|---|---|
|
|
521
521
|
| No images | text-to-video | 1–15s |
|
|
522
522
|
| 1 image (`sourceImage` or `sourceFilename`) | image-to-video | 1–15s |
|
|
523
|
-
| 2–
|
|
523
|
+
| 2–14 images (`referenceImages` / `referenceFilenames`) | reference-to-video | 1–15s on grok-imagine-video-1.5, 1–10s on grok-imagine-video |
|
|
524
524
|
|
|
525
525
|
1080p is accepted for `grok-imagine-video-1.5` prompt-only text-to-video and image-to-video with one image/frame source, including `continueFromVideo` after the server extracts the parent video's last frame. Prompt-only 1.5 text-to-video uses the internal white-canvas image-to-video shim before the upstream request. 1.5 does not add Ref2V, V2V edit, or extension support.
|
|
526
526
|
|
package/docs/API.zh-CN.md
CHANGED
|
@@ -386,7 +386,7 @@ Grok节点SSE回复不包括回复API `partial`图像事件是因为xAI图片API
|
|
|
386
386
|
|---|---|---|
|
|
387
387
|
|没有图片|文本转视频| 1–15s |
|
|
388
388
|
|1 张图片(`sourceImage`或者`sourceFilename`) |图像到视频| 1–15s |
|
|
389
|
-
|2–
|
|
389
|
+
|2–14 张图像 (`referenceImages` / `referenceFilenames`) |参考视频| 1.5: 1–15s / base: 1–10s |
|
|
390
390
|
|
|
391
391
|
1080p 可接受`grok-imagine-video-1.5`仅提示文本到视频和图像到视频,具有一个图像/帧源,包括`continueFromVideo`服务器提取父视频的最后一帧后。仅提示 1.5 文本到视频在上游请求之前使用内部白色画布图像到视频 shim。 1.5 不添加 Ref2V、V2V 编辑或扩展支持。
|
|
392
392
|
|
package/docs/API.zh-TW.md
CHANGED
|
@@ -386,7 +386,7 @@ Grok節點SSE回覆不包括回覆API `partial`影像事件是因為xAI圖片API
|
|
|
386
386
|
|---|---|---|
|
|
387
387
|
|沒有圖片|文字轉視頻| 1–15s |
|
|
388
388
|
|1 張圖片(`sourceImage`或者`sourceFilename`) |影像到視頻| 1–15s |
|
|
389
|
-
|2–
|
|
389
|
+
|2–14 張圖像 (`referenceImages` / `referenceFilenames`) |參考影片| 1.5: 1–15s / base: 1–10s |
|
|
390
390
|
|
|
391
391
|
1080p 可接受`grok-imagine-video-1.5`僅提示文字到視頻和圖像到視頻,具有一個圖像/幀源,包括`continueFromVideo`伺服器提取父影片的最後一幀後。僅提示 1.5 文字到影片在上游請求之前使用內部白色畫布圖像到影片 shim。 1.5 不新增 Ref2V、V2V 編輯或擴充支援。
|
|
392
392
|
|
package/docs/CLI.md
CHANGED
|
@@ -273,7 +273,7 @@ Video mode is auto-detected from `--ref` count:
|
|
|
273
273
|
|---|---|
|
|
274
274
|
| 0 | text-to-video |
|
|
275
275
|
| 1 | image-to-video |
|
|
276
|
-
| 2–
|
|
276
|
+
| 2–14 | reference-to-video (15s on grok-imagine-video-1.5, 10s on grok-imagine-video) |
|
|
277
277
|
|
|
278
278
|
`grok-imagine-video-1.5` supports 1080p for prompt-only text-to-video and single image/frame image-to-video. Prompt-only 1.5 text-to-video is submitted through the internal white-canvas image-to-video shim because upstream 1.5 rejects raw T2V. The old `grok-imagine-video-1.5-preview` name is accepted as an alias and normalized before the upstream request. 1.5 does not support `reference_images` reference-to-video, V2V edit, or video extension. For 2+ refs, use `grok-imagine-video`; if ima2 auto-retries a 1.5 Ref2V request to the base model, read `video.effectiveModel` and `video.modelFallback` from CLI `--json`, or `effectiveModel` and `modelFallback` from SSE.
|
|
279
279
|
|
package/docs/CLI.zh-CN.md
CHANGED
|
@@ -201,7 +201,7 @@ and rain build, end on a still close-up after the line finishes`。
|
|
|
201
201
|
|---|---|
|
|
202
202
|
| 0 |文本转视频|
|
|
203
203
|
| 1 |图像到视频|
|
|
204
|
-
| 2–
|
|
204
|
+
| 2–14 |参考视频(grok-imagine-video-1.5 最长 15 秒,grok-imagine-video 最长 10 秒)|
|
|
205
205
|
|
|
206
206
|
`grok-imagine-video-1.5`支持 1080p 仅提示文本到视频和单图像/帧图像到视频。仅提示 1.5 文本到视频通过内部白色画布图像到视频 shim 提交,因为上游 1.5 拒绝原始 T2V。旧的`grok-imagine-video-1.5-preview`name 在上游请求之前被接受为别名并进行规范化。 1.5 不支持`reference_images`视频参考、V2V 编辑或视频扩展。对于 2 个以上参考,请使用`grok-imagine-video`;如果ima2自动重试向基本模型发出 1.5 Ref2V 请求,读取`video.effectiveModel`和`video.modelFallback`从CLI `--json`, 或者`effectiveModel`和`modelFallback`从SSE.
|
|
207
207
|
|
package/docs/CLI.zh-TW.md
CHANGED
|
@@ -201,7 +201,7 @@ and rain build, end on a still close-up after the line finishes`。
|
|
|
201
201
|
|---|---|
|
|
202
202
|
| 0 |文字轉視頻|
|
|
203
203
|
| 1 |影像到視頻|
|
|
204
|
-
| 2–
|
|
204
|
+
| 2–14 |參考影片(grok-imagine-video-1.5 最長 15 秒,grok-imagine-video 最長 10 秒)|
|
|
205
205
|
|
|
206
206
|
`grok-imagine-video-1.5`支援 1080p 僅提示文字到影片和單圖像/幀圖像到影片。僅提示 1.5 文字到影片透過內部白色畫布圖像到影片 shim 提交,因為上游 1.5 拒絕原始 T2V。舊的`grok-imagine-video-1.5-preview`name 在上游請求之前被接受為別名並進行規範化。 1.5 不支持`reference_images`影片參考、V2V 編輯或影片擴充。對於 2 個以上參考,請使用`grok-imagine-video`;如果ima2自動重試向基本模型發出 1.5 Ref2V 請求,讀取`video.effectiveModel`和`video.modelFallback`從CLI `--json`, 或者`effectiveModel`和`modelFallback`從SSE.
|
|
207
207
|
|
package/docs/README.ja.md
CHANGED
|
@@ -120,7 +120,7 @@ API key が env/config に存在する場合、生成エンドポイントで `p
|
|
|
120
120
|
|
|
121
121
|
Settings に **API key provider available** と表示される場合、API key が検出され、生成・編集・multimode・node request に使用できるという意味です。
|
|
122
122
|
|
|
123
|
-
Grok 動画の既定値は正式名 `grok-imagine-video-1.5` です。`grok-imagine-video` は Ref2V、V2V edit、extension の互換パスで引き続き使用し、従来の `grok-imagine-video-1.5-preview` も互換 alias として受け付けます。参照数に応じて T2V(0)、I2V(1)、Ref2V(2-
|
|
123
|
+
Grok 動画の既定値は正式名 `grok-imagine-video-1.5` です。`grok-imagine-video` は Ref2V、V2V edit、extension の互換パスで引き続き使用し、従来の `grok-imagine-video-1.5-preview` も互換 alias として受け付けます。参照数に応じて T2V(0)、I2V(1)、Ref2V(2-14、grok-imagine-video-1.5 は最大15秒 / grok-imagine-video は最大10秒)が自動選択され、1080p は `grok-imagine-video-1.5` の prompt-only T2V と単一画像/フレーム I2V で有効です。duration(1-15s)、resolution(480p/720p/対応時 1080p)、aspect ratio を設定できます。
|
|
124
124
|
|
|
125
125
|
設定画面の QuotaCard に Grok billing `$used/$limit` バーと **Switch Account** ボタン(`POST /api/auth/switch`)が表示されます。
|
|
126
126
|
|
package/docs/README.ko.md
CHANGED
|
@@ -117,7 +117,7 @@ Ctrl+C로 DB, 소켓, 자식 프로세스를 정리할 수 있습니다. Windows
|
|
|
117
117
|
- **Video 생성**: 텍스트, 이미지, 또는 여러 레퍼런스에서 짧은 영상을 만듭니다. SSE로 기획→제출→진행률→완료를 실시간 표시합니다. 생성된 영상에서 First/Mid/Last 프레임 복사 버튼으로 키프레임을 추출할 수 있습니다.
|
|
118
118
|
- **Storyboard mode**: 컴포저에서 스토리보드 모드를 켜면 연속 프레임의 인물·장면 연속성을 유지합니다. 이미지와 영상 생성 모두 지원합니다.
|
|
119
119
|
- **Local gallery**: 생성물을 내 컴퓨터에 저장하고 세션별 히스토리로 봅니다. 기본적으로 현재 세션만 보이며 All Images 토글로 전체 히스토리를 볼 수 있습니다. 각 이미지의 생성 시간·reasoning effort가 메타데이터에 기록됩니다.
|
|
120
|
-
- **Reference images**: 레퍼런스를 드래그, 붙여넣기, 파일 선택으로 추가합니다. 이미지 최대 5장, 영상 최대
|
|
120
|
+
- **Reference images**: 레퍼런스를 드래그, 붙여넣기, 파일 선택으로 추가합니다. 이미지 최대 5장, 영상 최대 14장. 큰 이미지는 업로드 전에 자동 압축됩니다.
|
|
121
121
|
- **Prompt library imports**: 로컬 prompt pack, GitHub folder, curated GPT-image hint를 내장 prompt library로 가져옵니다.
|
|
122
122
|
- **Mobile shell**: 작은 화면에서는 app bar, compose sheet, compact settings toggle로 조작합니다.
|
|
123
123
|
- **Observable jobs**: 진행 중인 작업과 최근 완료된 작업을 request ID로 추적합니다.
|
|
@@ -139,7 +139,7 @@ Ctrl+C로 DB, 소켓, 자식 프로세스를 정리할 수 있습니다. Windows
|
|
|
139
139
|
|
|
140
140
|
Grok은 Classic, Node, Agent 흐름을 지원합니다. Classic 레퍼런스, Node 부모 이미지, Agent 현재 이미지가 있으면 최종 Grok 호출은 xAI image edit 경로로 전환되어 image-to-image 맥락을 유지합니다. 기본 이미지 모델은 `grok-imagine-image-quality`입니다.
|
|
141
141
|
|
|
142
|
-
Grok video 기본값은 정식 `grok-imagine-video-1.5`입니다. `grok-imagine-video`는 Ref2V, V2V edit, extension 호환 경로에서 계속 쓰며, 기존 `grok-imagine-video-1.5-preview` 문자열도 호환 alias로 받습니다. 레퍼런스 수에 따라 T2V(0), I2V(1), Ref2V(2-
|
|
142
|
+
Grok video 기본값은 정식 `grok-imagine-video-1.5`입니다. `grok-imagine-video`는 Ref2V, V2V edit, extension 호환 경로에서 계속 쓰며, 기존 `grok-imagine-video-1.5-preview` 문자열도 호환 alias로 받습니다. 레퍼런스 수에 따라 T2V(0), I2V(1), Ref2V(2-14, grok-imagine-video-1.5는 15초 / grok-imagine-video는 10초)가 자동 선택되며, 1080p는 `grok-imagine-video-1.5` 프롬프트 전용 T2V와 단일 이미지/프레임 I2V에서 활성화됩니다. 프롬프트 전용 1.5 T2V는 upstream 요청 전에 내부 흰 캔버스 I2V shim을 사용합니다. duration(1-15s), resolution(480p/720p/지원 시 1080p), aspect ratio 컨트롤을 제공합니다.
|
|
143
143
|
|
|
144
144
|
설정 화면의 QuotaCard에 Grok billing `$used/$limit` 바와 **Switch Account** 버튼(`POST /api/auth/switch`)이 표시됩니다.
|
|
145
145
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -176,7 +176,7 @@ ima2 skill install --tmp # install to temp dir (fallback)
|
|
|
176
176
|
|
|
177
177
|
Grok图像生成公开了模型选择器(`grok-imagine-image` / `grok-imagine-image-quality`)和尺寸选择器(长宽比 + 1k/2k 分辨率)。设置页面更喜欢Grok建立每周积分百分比并重置时间`GET /v1/billing?format=credits`;如果该来源不可用,则会退回到传统的每月计费窗口,并且`$used/$limit`. A **切换账户**按钮启动设备代码OAuth流动 (`POST /api/auth/switch`)无需离开应用程序即可重新进行身份验证。
|
|
178
178
|
|
|
179
|
-
Grok视频生成默认为规范`grok-imagine-video-1.5`; `grok-imagine-video`仍然可用于仅限基本模型的 Ref2V、V2V 编辑和扩展路径,以及旧版本`grok-imagine-video-1.5-preview`字符串被接受作为别名。根据引用计数自动检测三种模式:文本到视频(0 引用)、图像到视频(1 引用)和引用到视频(2-
|
|
179
|
+
Grok视频生成默认为规范`grok-imagine-video-1.5`; `grok-imagine-video`仍然可用于仅限基本模型的 Ref2V、V2V 编辑和扩展路径,以及旧版本`grok-imagine-video-1.5-preview`字符串被接受作为别名。根据引用计数自动检测三种模式:文本到视频(0 引用)、图像到视频(1 引用)和引用到视频(2-14 引用;grok-imagine-video-1.5 最长 15 秒,grok-imagine-video 最长 10 秒)。 1080p 可用于`grok-imagine-video-1.5`仅提示文本到视频和单图像/帧图像到视频;仅提示 1.5 在上游请求之前使用内部白色画布 I2V 填充程序。视频控制包括持续时间(1-15秒)、分辨率(480p、720p、1080p(如果支持))和宽高比(1:1、16:9、9:16、4:3、3:4、3:2、2:3、自动)。
|
|
180
180
|
|
|
181
181
|

|
|
182
182
|
|
package/docs/README.zh-TW.md
CHANGED
|
@@ -170,7 +170,7 @@ Web UI 使用單一 `GET /api/events` 伺服器發送事件(SSE)連線,接
|
|
|
170
170
|
|
|
171
171
|
Grok圖像生成公開了模型選擇器(`grok-imagine-image` / `grok-imagine-image-quality`)和尺寸選擇器(長寬比 + 1k/2k 解析度)。設定頁面更喜歡Grok建立每週積分百分比並重置時間`GET /v1/billing?format=credits`;如果該來源不可用,則會退回到傳統的每月計費窗口,並且`$used/$limit`. A **切換帳戶**按鈕啟動設備代碼OAuth流動 (`POST /api/auth/switch`)無需離開應用程式即可重新進行身份驗證。
|
|
172
172
|
|
|
173
|
-
Grok影片產生預設為規範`grok-imagine-video-1.5`; `grok-imagine-video`仍可用於僅限基本型號的 Ref2V、V2V 編輯和擴展路徑,以及舊版本`grok-imagine-video-1.5-preview`字串被接受作為別名。根據引用計數自動偵測三種模式:文字到影片(0 引用)、圖像到影片(1 引用)和引用到影片(2-
|
|
173
|
+
Grok影片產生預設為規範`grok-imagine-video-1.5`; `grok-imagine-video`仍可用於僅限基本型號的 Ref2V、V2V 編輯和擴展路徑,以及舊版本`grok-imagine-video-1.5-preview`字串被接受作為別名。根據引用計數自動偵測三種模式:文字到影片(0 引用)、圖像到影片(1 引用)和引用到影片(2-14 引用;grok-imagine-video-1.5 最長 15 秒,grok-imagine-video 最長 10 秒)。 1080p 可用於`grok-imagine-video-1.5`僅提示文字到影片和單圖像/幀圖像到影片;僅提示 1.5 在上游請求之前使用內部白色畫布 I2V 填充程式。視訊控制包括持續時間(1-15秒)、解析度(480p、720p、1080p(如果支援))和寬高比(1:1、16:9、9:16、4:3、3:4、3:2、2:3、自動)。
|
|
174
174
|
|
|
175
175
|

|
|
176
176
|
|
|
@@ -125,7 +125,7 @@ Status values to handle:
|
|
|
125
125
|
| default resolution | `480p` |
|
|
126
126
|
| video edit input max | 8.7 seconds — **owner-measured, not documented by xAI.** Re-verified as still-believed 2026-08-20; no public source states this number, so do not drop it just because doc sweeps come back empty |
|
|
127
127
|
| video extension input | 2-15 seconds |
|
|
128
|
-
| extension duration | 2-10 seconds |
|
|
128
|
+
| extension duration | 2-10 seconds (superseded: re-measured as 1-15 on 2026-09-08, see devlog/_plan/260908_xai_imagine_spec_resync) |
|
|
129
129
|
| generated URL | temporary; download promptly |
|
|
130
130
|
|
|
131
131
|
### Pricing Observed / Documented
|
|
@@ -4,7 +4,7 @@ Generated by `npm run test:inventory` (script: `scripts/classify-tests.mjs`).
|
|
|
4
4
|
|
|
5
5
|
_Tests considered "runtime-importing" if they import from `../lib/`, `../routes/`, `../bin/`, `../server`, or `../config`._
|
|
6
6
|
|
|
7
|
-
Total:
|
|
7
|
+
Total: 492 (runtime: 227, contract: 265)
|
|
8
8
|
|
|
9
9
|
## Runtime-importing tests
|
|
10
10
|
- `tests/agent-image-reference-contract.test.ts`
|
|
@@ -232,6 +232,8 @@ Total: 488 (runtime: 225, contract: 263)
|
|
|
232
232
|
- `tests/videoExtendedRoute.test.ts`
|
|
233
233
|
- `tests/videoLineage.test.ts`
|
|
234
234
|
- `tests/videoRoute.test.ts`
|
|
235
|
+
- `tests/voice-picker-contract.test.ts`
|
|
236
|
+
- `tests/xai-video-model-alias-contract.test.ts`
|
|
235
237
|
|
|
236
238
|
## Contract-only tests
|
|
237
239
|
- `tests/a11y-modal-contract.test.ts`
|
|
@@ -332,6 +334,7 @@ Total: 488 (runtime: 225, contract: 263)
|
|
|
332
334
|
- `tests/core-selection-transport.test.mjs`
|
|
333
335
|
- `tests/current-image-actions-readiness-contract.test.js`
|
|
334
336
|
- `tests/direct-mode-visual-contract.test.js`
|
|
337
|
+
- `tests/dropped-media-sorting-contract.test.ts`
|
|
335
338
|
- `tests/duration-slider-contract.test.js`
|
|
336
339
|
- `tests/e2e-app-environment.test.ts`
|
|
337
340
|
- `tests/e2e-runtime-build.test.mjs`
|
|
@@ -489,6 +492,7 @@ Total: 488 (runtime: 225, contract: 263)
|
|
|
489
492
|
- `tests/vectorize-panel-contract.test.ts`
|
|
490
493
|
- `tests/video-continuity-ui-contract.test.js`
|
|
491
494
|
- `tests/video-defaults-persistence-contract.test.js`
|
|
495
|
+
- `tests/video-edit-client-contract.test.ts`
|
|
492
496
|
- `tests/video-extend-ui-contract.test.js`
|
|
493
497
|
- `tests/video-frame-local-file.test.js`
|
|
494
498
|
- `tests/video-gallery-refresh-contract.test.ts`
|
package/lib/capabilities.js
CHANGED
|
@@ -5,7 +5,7 @@ import { buildCatalog, catalogSummary } from "./contracts/catalog.js";
|
|
|
5
5
|
import { loadAllBundledSnapshots } from "./mcp/snapshotStore.js";
|
|
6
6
|
import { KEY_TO_ENV, WRITABLE_CONFIG_KEYS } from "./configKeys.js";
|
|
7
7
|
import { DEFAULT_IMAGE_QUALITY, VALID_IMAGE_QUALITIES } from "./oauthNormalize.js";
|
|
8
|
-
import { MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, MAX_VIDEO_DURATION, MIN_VIDEO_DURATION } from "./imageModels.js";
|
|
8
|
+
import { MAX_REF2V_DURATION_15, MAX_REF2V_DURATION_BASE, MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, MAX_VIDEO_DURATION, MIN_VIDEO_DURATION, } from "./imageModels.js";
|
|
9
9
|
import { deriveProviderIds, getProviderSurfaceSupport } from "./providers/derive.js";
|
|
10
10
|
import { PROVIDER_SURFACES } from "./providers/surfaceSupport.js";
|
|
11
11
|
const VALID_MODES = ["auto", "direct"];
|
|
@@ -86,18 +86,21 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
|
|
|
86
86
|
},
|
|
87
87
|
videoModels: {
|
|
88
88
|
supported: ["grok-imagine-video", "grok-imagine-video-1.5"],
|
|
89
|
-
aliases: {
|
|
89
|
+
aliases: {
|
|
90
|
+
"grok-imagine-video-1.5-preview": "grok-imagine-video-1.5",
|
|
91
|
+
"grok-imagine-video-1.5-2026-05-30": "grok-imagine-video-1.5",
|
|
92
|
+
},
|
|
90
93
|
resolutions: ["480p", "720p", "1080p"],
|
|
91
94
|
resolutionNotes: { "1080p": "grok-imagine-video-1.5 text-to-video canvas shim or image-to-video; reference-to-video unsupported" },
|
|
92
95
|
aspectRatios: ["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "auto"],
|
|
93
96
|
durationRange: [1, 15],
|
|
94
|
-
maxReferences:
|
|
97
|
+
maxReferences: MAX_REF2V_REFERENCES,
|
|
95
98
|
// Flat numbers above describe the widest case, which is not what any single
|
|
96
99
|
// request is allowed to do. A client that reads only those draws controls the
|
|
97
100
|
// server will reject. These per-mode entries come from the same constants the
|
|
98
101
|
// request path enforces, so the advertisement cannot drift from the behavior.
|
|
99
|
-
// Verified against api.x.ai on 2026-08
|
|
100
|
-
// devlog/_plan/
|
|
102
|
+
// Verified against api.x.ai on 2026-09-08:
|
|
103
|
+
// devlog/_plan/260908_xai_imagine_spec_resync/000_research.md
|
|
101
104
|
modes: {
|
|
102
105
|
"text-to-video": {
|
|
103
106
|
maxReferences: 0,
|
|
@@ -113,9 +116,15 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
|
|
|
113
116
|
},
|
|
114
117
|
"reference-to-video": {
|
|
115
118
|
maxReferences: MAX_REF2V_REFERENCES,
|
|
116
|
-
|
|
119
|
+
// The widest case, which only grok-imagine-video-1.5 can actually reach.
|
|
120
|
+
// durationRangeByModel below is what a single request must obey.
|
|
121
|
+
durationRange: [MIN_VIDEO_DURATION, MAX_REF2V_DURATION_15],
|
|
122
|
+
durationRangeByModel: {
|
|
123
|
+
"grok-imagine-video-1.5": [MIN_VIDEO_DURATION, MAX_REF2V_DURATION_15],
|
|
124
|
+
"grok-imagine-video": [MIN_VIDEO_DURATION, MAX_REF2V_DURATION_BASE],
|
|
125
|
+
},
|
|
117
126
|
resolutions: ["480p", "720p"],
|
|
118
|
-
notes:
|
|
127
|
+
notes: `References guide the subject without locking the first frame. 1080p is rejected upstream. Up to ${MAX_REF2V_DURATION_15}s on grok-imagine-video-1.5, ${MAX_REF2V_DURATION_BASE}s on grok-imagine-video. A preset voice alone also selects this mode.`,
|
|
119
128
|
},
|
|
120
129
|
},
|
|
121
130
|
referenceAudio: {
|
|
@@ -130,6 +139,23 @@ export function buildIma2Capabilities({ appConfig = runtimeConfigDefault, packag
|
|
|
130
139
|
"atlas",
|
|
131
140
|
],
|
|
132
141
|
presetsAreAuthoritative: false,
|
|
142
|
+
// Named by the 400 itself: "Custom voice ids created via the /v1/custom-voices
|
|
143
|
+
// API are also accepted." The endpoint answers 200 with a 30-voice cap that is
|
|
144
|
+
// documented nowhere else.
|
|
145
|
+
customVoiceApi: "/v1/custom-voices",
|
|
146
|
+
},
|
|
147
|
+
// Which model takes a video in, and which takes audio in. The two split on input
|
|
148
|
+
// modality, so there is no path that edits a video on 1.5 — it answers 400.
|
|
149
|
+
videoInputModes: {
|
|
150
|
+
edit: {
|
|
151
|
+
models: ["grok-imagine-video"],
|
|
152
|
+
notes: "grok-imagine-video-1.5 answers 400 'Video editing is not supported for this model.'",
|
|
153
|
+
},
|
|
154
|
+
extend: {
|
|
155
|
+
models: ["grok-imagine-video"],
|
|
156
|
+
durationRange: [MIN_VIDEO_DURATION, MAX_VIDEO_DURATION],
|
|
157
|
+
notes: "duration is the added segment, not the total. Measured 1-15s, wider than the documented 2-10.",
|
|
158
|
+
},
|
|
133
159
|
},
|
|
134
160
|
},
|
|
135
161
|
reasoningEfforts: toArray(appConfig.imageModels.validReasoningEfforts),
|
package/lib/grokVideoAdapter.js
CHANGED
|
@@ -5,7 +5,7 @@ import { detectImageMimeFromB64 } from "./refs.js";
|
|
|
5
5
|
import { aspectToCanvas, generateWhiteCanvasB64 } from "./grokVideoCanvas.js";
|
|
6
6
|
import { downloadVideo } from "./grokVideoDownload.js";
|
|
7
7
|
import { buildGrokVideoPlannerSystemPrompt, composeFallbackVideoPrompt, formatDurationPacingGuidance } from "./grokVideoPlannerPrompt.js";
|
|
8
|
-
import { GROK_VIDEO_MODEL_15,
|
|
8
|
+
import { GROK_VIDEO_MODEL_15, GROK_VIDEO_MODEL_BASE, MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, canonicalGrokVideoModel, validateVideoDurationForRequest, validateVideoResolutionForRequest, } from "./imageModels.js";
|
|
9
9
|
import { formatVideoContinuityForPlanner } from "./videoContinuity.js";
|
|
10
10
|
import { DEFAULT_GROK_PLANNER_MODEL } from "../config.js";
|
|
11
11
|
import { videoConfig, videoEndpoint, withTimeoutSignal, } from "./grokVideoShared.js";
|
|
@@ -41,9 +41,7 @@ export function isDegradablePlannerFailure(err) {
|
|
|
41
41
|
return { reason: "failed", message };
|
|
42
42
|
return null;
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
return model === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : model;
|
|
46
|
-
}
|
|
44
|
+
const canonicalVideoModel = canonicalGrokVideoModel;
|
|
47
45
|
function sourceImageUrl(image, mime) {
|
|
48
46
|
if (image.startsWith("data:") || image.startsWith("http"))
|
|
49
47
|
return image;
|
|
@@ -314,6 +312,13 @@ export function buildVideoGenerationPayload(plan, opts) {
|
|
|
314
312
|
if (!("ok" in resolutionCheck)) {
|
|
315
313
|
throw grokError(resolutionCheck.error, resolutionCheck.status, resolutionCheck.code);
|
|
316
314
|
}
|
|
315
|
+
// Answered here as well as at the route because the agent path builds payloads without
|
|
316
|
+
// going through routes/video.ts. Two checks of the same rule are two defenses, not a
|
|
317
|
+
// duplicated rule: both read the same function.
|
|
318
|
+
const durationCheck = validateVideoDurationForRequest(model, plan.duration, plan.mode);
|
|
319
|
+
if (!("ok" in durationCheck)) {
|
|
320
|
+
throw grokError(durationCheck.error, durationCheck.status, durationCheck.code);
|
|
321
|
+
}
|
|
317
322
|
const payload = { model, prompt: plan.prompt, duration: plan.duration, resolution: plan.resolution };
|
|
318
323
|
if (plan.aspectRatio && plan.aspectRatio !== "auto")
|
|
319
324
|
payload.aspect_ratio = plan.aspectRatio;
|
package/lib/imageModels.js
CHANGED
|
@@ -171,10 +171,15 @@ export function normalizeComfyWorkflowModel(rawModel) {
|
|
|
171
171
|
export const GROK_VIDEO_MODEL_BASE = "grok-imagine-video";
|
|
172
172
|
export const GROK_VIDEO_MODEL_15 = "grok-imagine-video-1.5";
|
|
173
173
|
export const GROK_VIDEO_MODEL_15_PREVIEW_ALIAS = "grok-imagine-video-1.5-preview";
|
|
174
|
+
// GET /v1/video-generation-models lists this alongside -preview. Without it a legal
|
|
175
|
+
// model id is rejected locally with INVALID_GROK_VIDEO_MODEL before the request ever
|
|
176
|
+
// reaches xAI. Verified 2026-09-08: devlog/_plan/260908_xai_imagine_spec_resync.
|
|
177
|
+
export const GROK_VIDEO_MODEL_15_DATED_ALIAS = "grok-imagine-video-1.5-2026-05-30";
|
|
174
178
|
export const GROK_FALLBACK_VIDEO_MODEL = GROK_VIDEO_MODEL_15;
|
|
175
179
|
export const VALID_GROK_VIDEO_MODELS = new Set([
|
|
176
180
|
...deriveModels("grok", "video"),
|
|
177
181
|
GROK_VIDEO_MODEL_15_PREVIEW_ALIAS,
|
|
182
|
+
GROK_VIDEO_MODEL_15_DATED_ALIAS,
|
|
178
183
|
]);
|
|
179
184
|
export const VALID_VIDEO_RESOLUTIONS = new Set(["480p", "720p", "1080p"]);
|
|
180
185
|
export const VALID_VIDEO_ASPECT_RATIOS = new Set([
|
|
@@ -189,10 +194,25 @@ export const VALID_VIDEO_ASPECT_RATIOS = new Set([
|
|
|
189
194
|
]);
|
|
190
195
|
export const MIN_VIDEO_DURATION = 1;
|
|
191
196
|
export const MAX_VIDEO_DURATION = 15;
|
|
192
|
-
// reference-to-video (xAI): up to
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
|
|
197
|
+
// reference-to-video (xAI): up to 14 reference images, 720p max.
|
|
198
|
+
//
|
|
199
|
+
// The cap doubled from 7. 15 returns 400 "Too many reference images: 15. Maximum
|
|
200
|
+
// allowed is 14." and 30 names the same number, on BOTH video models, so it is a
|
|
201
|
+
// model-independent limit. @imagine announced it on 2026-09-02; the API docs still
|
|
202
|
+
// say 7, which is why the measurement is the source here and not the page.
|
|
203
|
+
// Re-verified against api.x.ai on 2026-09-08:
|
|
204
|
+
// devlog/_plan/260908_xai_imagine_spec_resync/000_research.md
|
|
205
|
+
export const MAX_REF2V_REFERENCES = 14;
|
|
206
|
+
// reference-to-video carries its own duration ceiling, and it differs by model. The
|
|
207
|
+
// same 16s request answers "maximum allowed for reference-to-video, which is 15s" on
|
|
208
|
+
// grok-imagine-video-1.5 and "... is 10s" on grok-imagine-video.
|
|
209
|
+
//
|
|
210
|
+
// This is NOT the ceiling removed in 260820. That one was invented locally and applied
|
|
211
|
+
// to every model against evidence. This one is the upstream's own per-model rule, and
|
|
212
|
+
// omitting it turns a legal-looking base-model request into an upstream 400 the user
|
|
213
|
+
// pays a planning round for.
|
|
214
|
+
export const MAX_REF2V_DURATION_15 = 15;
|
|
215
|
+
export const MAX_REF2V_DURATION_BASE = 10;
|
|
196
216
|
// reference_audios: preset voices, grok-imagine-video-1.5 only. 4 -> 400.
|
|
197
217
|
export const MAX_REFERENCE_AUDIOS = 3;
|
|
198
218
|
/**
|
|
@@ -216,6 +236,55 @@ export function deriveVideoMode(refCount) {
|
|
|
216
236
|
export function isGrokVideoModel(value) {
|
|
217
237
|
return typeof value === "string" && VALID_GROK_VIDEO_MODELS.has(value);
|
|
218
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Folds every alias onto the id xAI bills and validates against.
|
|
241
|
+
*
|
|
242
|
+
* Three call sites used to inline `model === PREVIEW_ALIAS ? GROK_VIDEO_MODEL_15 : model`,
|
|
243
|
+
* which is exactly one alias' worth of knowledge duplicated three times. Adding the dated
|
|
244
|
+
* alias would have had to find all three; one of them would have been missed.
|
|
245
|
+
*
|
|
246
|
+
* Passes non-Grok ids through unchanged: comfy hands a workflow id down this same
|
|
247
|
+
* `model` field (routes/video.ts), and rewriting it would be worse than leaving it alone.
|
|
248
|
+
*/
|
|
249
|
+
export function canonicalGrokVideoModel(model) {
|
|
250
|
+
return model === GROK_VIDEO_MODEL_15_PREVIEW_ALIAS || model === GROK_VIDEO_MODEL_15_DATED_ALIAS
|
|
251
|
+
? GROK_VIDEO_MODEL_15
|
|
252
|
+
: model;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The reference-to-video duration ceiling for a model, or null when the rule does not
|
|
256
|
+
* apply to it.
|
|
257
|
+
*
|
|
258
|
+
* Null is not "unknown" and not "no limit": it means xAI's per-model r2v rule has no
|
|
259
|
+
* jurisdiction here. A comfy workflow id arrives through the same `model` field, and
|
|
260
|
+
* answering the base model's 10s for it would clamp a lane whose ceiling xAI does not
|
|
261
|
+
* own. Callers must handle null explicitly rather than let it fall through to a number.
|
|
262
|
+
*/
|
|
263
|
+
export function maxRef2vDuration(model) {
|
|
264
|
+
if (!isGrokVideoModel(model))
|
|
265
|
+
return null;
|
|
266
|
+
return canonicalGrokVideoModel(model) === GROK_VIDEO_MODEL_15
|
|
267
|
+
? MAX_REF2V_DURATION_15
|
|
268
|
+
: MAX_REF2V_DURATION_BASE;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Validates a duration against the ceiling its mode and model actually impose.
|
|
272
|
+
*
|
|
273
|
+
* Returns ok for every non-reference-to-video request: normalizeVideoDuration already
|
|
274
|
+
* owns the shared 1-15 bound, and re-checking it here would give one rule two owners.
|
|
275
|
+
*/
|
|
276
|
+
export function validateVideoDurationForRequest(model, duration, mode) {
|
|
277
|
+
if (mode !== "reference-to-video")
|
|
278
|
+
return { ok: true };
|
|
279
|
+
const ceiling = maxRef2vDuration(model);
|
|
280
|
+
if (ceiling === null || duration <= ceiling)
|
|
281
|
+
return { ok: true };
|
|
282
|
+
return {
|
|
283
|
+
error: `reference-to-video on ${model} allows at most ${ceiling} seconds`,
|
|
284
|
+
code: "INVALID_VIDEO_DURATION",
|
|
285
|
+
status: 400,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
219
288
|
export function normalizeGrokVideoModel(rawModel) {
|
|
220
289
|
if (typeof rawModel !== "string" || rawModel.length === 0) {
|
|
221
290
|
return { model: GROK_FALLBACK_VIDEO_MODEL };
|
|
@@ -227,7 +296,7 @@ export function normalizeGrokVideoModel(rawModel) {
|
|
|
227
296
|
status: 400,
|
|
228
297
|
};
|
|
229
298
|
}
|
|
230
|
-
return { model: rawModel
|
|
299
|
+
return { model: canonicalGrokVideoModel(rawModel) };
|
|
231
300
|
}
|
|
232
301
|
export function normalizeVideoResolution(raw) {
|
|
233
302
|
if (raw === undefined || raw === null || raw === "")
|
|
@@ -242,13 +311,17 @@ export function normalizeVideoResolution(raw) {
|
|
|
242
311
|
return { resolution: raw };
|
|
243
312
|
}
|
|
244
313
|
export function usesGrokVideo15TextCanvasShim(model, mode) {
|
|
245
|
-
|
|
246
|
-
return canonicalModel === GROK_VIDEO_MODEL_15 && mode === "text-to-video";
|
|
314
|
+
return canonicalGrokVideoModel(model) === GROK_VIDEO_MODEL_15 && mode === "text-to-video";
|
|
247
315
|
}
|
|
248
316
|
export function validateVideoResolutionForRequest(model, resolution, mode, options = {}) {
|
|
249
317
|
if (resolution !== "1080p")
|
|
250
318
|
return { ok: true };
|
|
251
|
-
|
|
319
|
+
// A non-Grok model reaches this function too: comfy passes its workflow id through the
|
|
320
|
+
// same field. xAI's 1080p rule says nothing about a locally-run workflow, so refusing
|
|
321
|
+
// one here would be this codebase inventing a limit rather than enforcing a real one.
|
|
322
|
+
if (!isGrokVideoModel(model))
|
|
323
|
+
return { ok: true };
|
|
324
|
+
const canonicalModel = canonicalGrokVideoModel(model);
|
|
252
325
|
if (canonicalModel === GROK_VIDEO_MODEL_15 && mode === "image-to-video") {
|
|
253
326
|
return { ok: true };
|
|
254
327
|
}
|
|
@@ -68,9 +68,13 @@ export const REGISTRY = [
|
|
|
68
68
|
{ id: "grok-imagine-image", kind: "image", supports: EDIT },
|
|
69
69
|
{ id: "grok-imagine-image-quality", kind: "image", supports: EDIT },
|
|
70
70
|
{ id: "grok-imagine-video", kind: "video", supports: EDIT },
|
|
71
|
-
{ id: "grok-imagine-video-1.5", aliases: ["grok-imagine-video-1.5-preview"], kind: "video", supports: EDIT },
|
|
71
|
+
{ id: "grok-imagine-video-1.5", aliases: ["grok-imagine-video-1.5-preview", "grok-imagine-video-1.5-2026-05-30"], kind: "video", supports: EDIT },
|
|
72
72
|
],
|
|
73
|
-
|
|
73
|
+
// image/edit 5: measured on grok-imagine-image-2.0 through the oauth proxy on
|
|
74
|
+
// 2026-09-08 (6 -> 400 "This model supports at most 5 input image(s)"), and xAI's
|
|
75
|
+
// release notes say editing "now accepts up to 5 source images per request (was 3)".
|
|
76
|
+
// video 14: see MAX_REF2V_REFERENCES in lib/imageModels.ts.
|
|
77
|
+
referenceLimits: { image: 5, edit: 5, video: 14 },
|
|
74
78
|
elementTaxonomy: "grok",
|
|
75
79
|
limits: { timeoutMs: 300_000 },
|
|
76
80
|
errorPrefix: "GROK_",
|
|
@@ -92,9 +96,12 @@ export const REGISTRY = [
|
|
|
92
96
|
{ id: "grok-imagine-image", kind: "image", supports: EDIT },
|
|
93
97
|
{ id: "grok-imagine-image-quality", kind: "image", supports: EDIT },
|
|
94
98
|
{ id: "grok-imagine-video", kind: "video", supports: EDIT },
|
|
95
|
-
{ id: "grok-imagine-video-1.5", aliases: ["grok-imagine-video-1.5-preview"], kind: "video", supports: EDIT },
|
|
99
|
+
{ id: "grok-imagine-video-1.5", aliases: ["grok-imagine-video-1.5-preview", "grok-imagine-video-1.5-2026-05-30"], kind: "video", supports: EDIT },
|
|
96
100
|
],
|
|
97
|
-
|
|
101
|
+
// Same upstream as the grok lane, reached with a direct key instead of the proxy, so
|
|
102
|
+
// the caps are taken to match. The 5 was measured on the proxy path only; if an edit
|
|
103
|
+
// fails at 4-5 images with a direct key, this assumption is the place to look.
|
|
104
|
+
referenceLimits: { image: 5, edit: 5, video: 14 },
|
|
98
105
|
elementTaxonomy: "grok",
|
|
99
106
|
limits: { timeoutMs: 300_000 },
|
|
100
107
|
errorPrefix: "GROK_",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ima2-gen",
|
|
3
|
-
"version": "3.15.
|
|
3
|
+
"version": "3.15.1",
|
|
4
4
|
"packageManager": "npm@11.18.0",
|
|
5
5
|
"description": "Local-first visual generation runtime and studio for people and coding agents, with reproducible image and video workflows across multiple providers.",
|
|
6
6
|
"type": "module",
|
|
@@ -127,5 +127,5 @@
|
|
|
127
127
|
"typescript": "^5.9.3",
|
|
128
128
|
"yaml": "2.9.0"
|
|
129
129
|
},
|
|
130
|
-
"gitHead": "
|
|
130
|
+
"gitHead": "11900764a59e74146c9008ed296a23d3fc0ac06a"
|
|
131
131
|
}
|
package/routes/video.js
CHANGED
|
@@ -16,7 +16,7 @@ import { getVideoSeriesChain } from "../lib/videoSeriesChain.js";
|
|
|
16
16
|
import { ACTIVE_VIDEO_PROMPT_GUIDANCE, appendVideoContinuityEntry, lineageFromVideoMetadata, normalizeVideoContinuityLineage, readVideoSidecar, requireActiveVideoPrompt, safeGeneratedVideoFilename, } from "../lib/videoContinuity.js";
|
|
17
17
|
import { extractGeneratedVideoFrameB64 } from "../lib/videoFrameExtract.js";
|
|
18
18
|
import { errorEnvelopeFields } from "../lib/errors/envelope.js";
|
|
19
|
-
import { normalizeGrokVideoModel, normalizeVideoResolution, normalizeVideoAspectRatio, normalizeVideoDuration, deriveVideoMode, MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, validateVideoResolutionForRequest, } from "../lib/imageModels.js";
|
|
19
|
+
import { normalizeGrokVideoModel, normalizeVideoResolution, normalizeVideoAspectRatio, normalizeVideoDuration, deriveVideoMode, MAX_REF2V_REFERENCES, MAX_REFERENCE_AUDIOS, validateVideoResolutionForRequest, validateVideoDurationForRequest, } from "../lib/imageModels.js";
|
|
20
20
|
import { errInfo } from "../lib/errInfo.js";
|
|
21
21
|
import { requireRuntimeContext } from "../lib/runtimeContext.js";
|
|
22
22
|
import { generateVideoThumbnail } from "../lib/videoThumb.js";
|
|
@@ -338,18 +338,36 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
338
338
|
// devlog/_plan/260820_grok15_multi_reference_video/030_single_ref_mode_choice.md
|
|
339
339
|
const composerRefCount = resolved.filter((r) => r.source === "composer").length;
|
|
340
340
|
const requestedMode = typeof req.body?.mode === "string" ? req.body.mode : null;
|
|
341
|
+
// Shape only. Which voice ids exist is xAI's to answer, and its 400 names every
|
|
342
|
+
// valid voice — a list we would only get wrong, and which cannot include the
|
|
343
|
+
// caller's custom voices anyway. Parsed here rather than at the call site because
|
|
344
|
+
// the mode derivation below needs to know whether any voice was attached.
|
|
345
|
+
const referenceAudios = toArray(req.body?.referenceAudios)
|
|
346
|
+
.map((voice) => (typeof voice === "string" ? voice.trim() : ""))
|
|
347
|
+
.filter((voice) => voice.length > 0);
|
|
348
|
+
if (referenceAudios.length > MAX_REFERENCE_AUDIOS) {
|
|
349
|
+
return fail(400, "GROK_VIDEO_AUDIO_TOO_MANY", `at most ${MAX_REFERENCE_AUDIOS} reference voices`);
|
|
350
|
+
}
|
|
341
351
|
const derivedMode = composerRefCount > 0
|
|
342
352
|
? "reference-to-video"
|
|
343
|
-
:
|
|
353
|
+
// A voice alone selects reference-to-video upstream: "at least one reference of
|
|
354
|
+
// either kind selects the reference-to-video mode" (xAI OpenAPI,
|
|
355
|
+
// GenerateVideoRequest.reference_audios). Calling it text-to-video here would let
|
|
356
|
+
// a 15s base-model request through while xAI applies the 10s r2v ceiling, and the
|
|
357
|
+
// rejection would name a mode this request never asked for.
|
|
358
|
+
: referenceAudios.length > 0
|
|
359
|
+
? "reference-to-video"
|
|
360
|
+
: deriveVideoMode(resolved.length);
|
|
344
361
|
const mode = incomingProviderUrl
|
|
345
362
|
? "image-to-video"
|
|
346
363
|
: (requestedMode === "reference-to-video" || requestedMode === "image-to-video" || requestedMode === "text-to-video")
|
|
347
364
|
? requestedMode
|
|
348
365
|
: derivedMode;
|
|
349
|
-
// An explicit reference-to-video with nothing to reference would ship
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
366
|
+
// An explicit reference-to-video with nothing to reference at all would ship empty
|
|
367
|
+
// arrays and fail upstream with a less useful message. A voice counts as a
|
|
368
|
+
// reference, so audio-only is legitimate and must not be rejected here.
|
|
369
|
+
if (mode === "reference-to-video" && resolved.length === 0 && referenceAudios.length === 0) {
|
|
370
|
+
return fail(400, "GROK_VIDEO_INVALID_MODE", "reference-to-video requires at least 1 reference image or voice");
|
|
353
371
|
}
|
|
354
372
|
const duration = durationCheck.duration;
|
|
355
373
|
const resolutionModeCheck = validateVideoResolutionForRequest(modelCheck.model, resolutionCheck.resolution, mode, {
|
|
@@ -357,6 +375,12 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
357
375
|
});
|
|
358
376
|
if (isNormalizeError(resolutionModeCheck))
|
|
359
377
|
return fail(resolutionModeCheck.status, resolutionModeCheck.code, resolutionModeCheck.error);
|
|
378
|
+
// The r2v ceiling is per model, and the start call is deliberately never retried
|
|
379
|
+
// (a retry could bill a second video), so a 400 we can predict must be answered
|
|
380
|
+
// before the job is admitted rather than asynchronously after planning.
|
|
381
|
+
const durationModeCheck = validateVideoDurationForRequest(modelCheck.model, duration, mode);
|
|
382
|
+
if (isNormalizeError(durationModeCheck))
|
|
383
|
+
return fail(durationModeCheck.status, durationModeCheck.code, durationModeCheck.error);
|
|
360
384
|
const referenceImages = mode === "reference-to-video" ? resolved.map((r) => r.b64) : undefined;
|
|
361
385
|
const sourceB64 = incomingProviderUrl || (mode === "image-to-video" ? resolved[0]?.b64 : undefined);
|
|
362
386
|
const sourceFilename = resolved[0]?.filename ?? null;
|
|
@@ -483,15 +507,6 @@ export function registerVideoRoutes(app, ctxRaw) {
|
|
|
483
507
|
+ (backgroundPreset ? ` ${backgroundPromptSuffix(backgroundPreset, "video")}` : "");
|
|
484
508
|
const plannerModel = typeof req.body?.plannerModel === "string" ? req.body.plannerModel.trim() : undefined;
|
|
485
509
|
const directApiKey = provider === "grok-api" ? ctx.xaiApiKey : undefined;
|
|
486
|
-
// Only the shape is checked here. Which voice ids exist is xAI's to answer, and its
|
|
487
|
-
// 400 names every valid voice — a list we would only get wrong, and which cannot
|
|
488
|
-
// include the caller's custom voices anyway.
|
|
489
|
-
const referenceAudios = toArray(req.body?.referenceAudios)
|
|
490
|
-
.map((voice) => (typeof voice === "string" ? voice.trim() : ""))
|
|
491
|
-
.filter((voice) => voice.length > 0);
|
|
492
|
-
if (referenceAudios.length > MAX_REFERENCE_AUDIOS) {
|
|
493
|
-
return fail(400, "GROK_VIDEO_AUDIO_TOO_MANY", `at most ${MAX_REFERENCE_AUDIOS} reference voices`);
|
|
494
|
-
}
|
|
495
510
|
const result = await generateVideoViaGrok(effectivePrompt, ctx, {
|
|
496
511
|
model: modelCheck.model,
|
|
497
512
|
mode,
|