everypixel-cli 0.1.0__tar.gz → 0.2.1__tar.gz

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 (24) hide show
  1. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/CHANGELOG.md +11 -0
  2. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/PKG-INFO +51 -1
  3. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/README.md +50 -0
  4. everypixel_cli-0.2.1/src/everypixel_cli/__init__.py +1 -0
  5. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/application/services.py +136 -28
  6. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/cli.py +69 -11
  7. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/mcp_server.py +35 -5
  8. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/resources/openapi.json +662 -109
  9. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/schemas.py +268 -75
  10. everypixel_cli-0.1.0/src/everypixel_cli/__init__.py +0 -1
  11. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/.gitignore +0 -0
  12. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/LICENSE +0 -0
  13. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/pyproject.toml +0 -0
  14. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/__main__.py +0 -0
  15. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/application/__init__.py +0 -0
  16. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/application/models.py +0 -0
  17. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/application/serialization.py +0 -0
  18. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/client.py +0 -0
  19. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/config.py +0 -0
  20. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/errors.py +0 -0
  21. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/files.py +0 -0
  22. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/openapi.py +0 -0
  23. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/output.py +0 -0
  24. {everypixel_cli-0.1.0 → everypixel_cli-0.2.1}/src/everypixel_cli/resources/__init__.py +0 -0
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1] - 2026-09-11
4
+
5
+ - Support up to 9 image, 3 video, and 3 audio references for MiniMax H3/Turbo.
6
+
7
+ ## [0.2.0] - 2026-09-09
8
+
9
+ - Add MiniMax H3/Turbo, WAN 3.0, Flux 3, and Seedance 2.5 video models.
10
+ - Add Recraft vector generation, palette controls, and image-to-SVG conversion.
11
+ - Update Grok Imagine models, image-edit input limits, and megapixel controls.
12
+ - Add Topaz 4K video upscaling and 720p lipsync.
13
+
3
14
  ## [0.1.0] - 2026-08-17
4
15
 
5
16
  Initial public release.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: everypixel-cli
3
- Version: 0.1.0
3
+ Version: 0.2.1
4
4
  Summary: Official command line interface for Everypixel API
5
5
  Project-URL: Homepage, https://labs.everypixel.com
6
6
  Project-URL: Repository, https://github.com/everypixel/everypixel-cli
@@ -144,6 +144,7 @@ CLI falls back to its user configuration file when a keyring is unavailable;
144
144
  | --- | --- |
145
145
  | `everypixel image generate PROMPT` | Generate an image from a text prompt |
146
146
  | `everypixel image edit PROMPT --image FILE` | Edit one or more images |
147
+ | `everypixel image vectorize FILE` | Convert an image to SVG |
147
148
  | `everypixel image upscale` | Upscale an image, URL, or existing task result |
148
149
  | `everypixel image angles --image FILE` | Change the camera angle of an image |
149
150
  | `everypixel image colors --image FILE --reference FILE` | Transfer colors from a reference image |
@@ -159,6 +160,13 @@ everypixel image edit \
159
160
 
160
161
  everypixel image upscale --image ./input.png --download ./outputs
161
162
 
163
+ everypixel image generate "a mountain icon" \
164
+ --model recraftv4_1_vector \
165
+ --controls '{"colors":[{"rgb":[20,100,180],"weight":1}]}' \
166
+ --download ./outputs
167
+
168
+ everypixel image vectorize ./input.png --download ./outputs
169
+
162
170
  everypixel image angles \
163
171
  --image ./input.png \
164
172
  --azimuth right \
@@ -167,6 +175,16 @@ everypixel image angles \
167
175
  --download ./outputs
168
176
  ```
169
177
 
178
+ Recraft V4.1 vector models (`recraftv4_1_vector`, `recraftv4_1_pro_vector`)
179
+ produce SVG files and accept optional JSON palette `--controls`. Use `--size`
180
+ to select their aspect ratio. Raster models also accept `--resolution`.
181
+
182
+ `--lora-url` is supported only by `zimage`.
183
+
184
+ Grok image models are `grok-imagine`, `grok-imagine-2`, and
185
+ `grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
186
+ `wan22` have been removed from the API.
187
+
170
188
  ### Video
171
189
 
172
190
  | Command | Description |
@@ -197,8 +215,38 @@ everypixel video edit \
197
215
  "make the scene cinematic" \
198
216
  --video ./input.mp4 \
199
217
  --download ./outputs
218
+
219
+ everypixel video generate "a slow aerial shot over mountain lakes" \
220
+ --model minimax-h3-turbo --duration 10 --download ./outputs
221
+
222
+ everypixel video edit "continue the scene with a slow camera pan" \
223
+ --model flux3 --video ./input.mp4 --download ./outputs
224
+
225
+ everypixel video upscale --video ./input.mp4 \
226
+ --model topaz-prob-4 --resolution 4k --download ./outputs
200
227
  ```
201
228
 
229
+ New video models use these provider defaults and limits:
230
+
231
+ | Model | Duration | Default resolution / aspect ratio | Inputs |
232
+ | --- | --- | --- | --- |
233
+ | `minimax-h3-turbo`, `minimax-h3` | 3–15 s | 768p / 7:4 | Text, first/last frames, or up to 9 image, 3 video, and 3 audio references |
234
+ | `wan3.0` | 2–30 s | 1080p / adaptive | Text, frames, or up to 10 reference images and 5 reference videos |
235
+ | `flux3` | 5–20 s generation; 5–15 s continuation | 720p / 16:9 | Text, frames, or `video edit --video` for continuation |
236
+ | `seedance2.5` | 4–30 s | 720p / 16:9 | Text or `video edit` references, including audio alone |
237
+
238
+ Use repeatable `--reference-image`, `--reference-video`, and `--reference-audio`
239
+ options for MiniMax references. References cannot be combined with first/last
240
+ frames. WAN 3.0 also separates frame and reference inputs. Seedance 2.5 supports
241
+ up to 1080p;
242
+ Seedance 2 and Mini retain their 15-second limit. Grok video model names are
243
+ `grok-imagine` and `grok-imagine-1.5` (image required); `grok` and `grok15`
244
+ remain accepted aliases. The CLI's default video model remains `ltx23`.
245
+
246
+ Topaz upscaling models are `topaz-prob-4`, `topaz-slp-2.5`, and `topaz-ast-2`.
247
+ They support 4K output. The default `seedvr2` supports up to 1440p, with a
248
+ 20-second source limit at 1080p and a 10-second limit at 1440p, inclusive.
249
+
202
250
  ### Lipsync and Audio
203
251
 
204
252
  | Command | Description |
@@ -210,6 +258,8 @@ everypixel video edit \
210
258
  | `everypixel audio tts-clone` | Create speech from a cloned voice sample |
211
259
  | `everypixel audio tts-voice` | Create speech with a character voice |
212
260
 
261
+ Lipsync supports 720p; the API checks source media dimensions and duration.
262
+
213
263
  Examples:
214
264
 
215
265
  ```bash
@@ -110,6 +110,7 @@ CLI falls back to its user configuration file when a keyring is unavailable;
110
110
  | --- | --- |
111
111
  | `everypixel image generate PROMPT` | Generate an image from a text prompt |
112
112
  | `everypixel image edit PROMPT --image FILE` | Edit one or more images |
113
+ | `everypixel image vectorize FILE` | Convert an image to SVG |
113
114
  | `everypixel image upscale` | Upscale an image, URL, or existing task result |
114
115
  | `everypixel image angles --image FILE` | Change the camera angle of an image |
115
116
  | `everypixel image colors --image FILE --reference FILE` | Transfer colors from a reference image |
@@ -125,6 +126,13 @@ everypixel image edit \
125
126
 
126
127
  everypixel image upscale --image ./input.png --download ./outputs
127
128
 
129
+ everypixel image generate "a mountain icon" \
130
+ --model recraftv4_1_vector \
131
+ --controls '{"colors":[{"rgb":[20,100,180],"weight":1}]}' \
132
+ --download ./outputs
133
+
134
+ everypixel image vectorize ./input.png --download ./outputs
135
+
128
136
  everypixel image angles \
129
137
  --image ./input.png \
130
138
  --azimuth right \
@@ -133,6 +141,16 @@ everypixel image angles \
133
141
  --download ./outputs
134
142
  ```
135
143
 
144
+ Recraft V4.1 vector models (`recraftv4_1_vector`, `recraftv4_1_pro_vector`)
145
+ produce SVG files and accept optional JSON palette `--controls`. Use `--size`
146
+ to select their aspect ratio. Raster models also accept `--resolution`.
147
+
148
+ `--lora-url` is supported only by `zimage`.
149
+
150
+ Grok image models are `grok-imagine`, `grok-imagine-2`, and
151
+ `grok-imagine-2-low`. The `grok` alias remains supported. `grok_quality` and
152
+ `wan22` have been removed from the API.
153
+
136
154
  ### Video
137
155
 
138
156
  | Command | Description |
@@ -163,8 +181,38 @@ everypixel video edit \
163
181
  "make the scene cinematic" \
164
182
  --video ./input.mp4 \
165
183
  --download ./outputs
184
+
185
+ everypixel video generate "a slow aerial shot over mountain lakes" \
186
+ --model minimax-h3-turbo --duration 10 --download ./outputs
187
+
188
+ everypixel video edit "continue the scene with a slow camera pan" \
189
+ --model flux3 --video ./input.mp4 --download ./outputs
190
+
191
+ everypixel video upscale --video ./input.mp4 \
192
+ --model topaz-prob-4 --resolution 4k --download ./outputs
166
193
  ```
167
194
 
195
+ New video models use these provider defaults and limits:
196
+
197
+ | Model | Duration | Default resolution / aspect ratio | Inputs |
198
+ | --- | --- | --- | --- |
199
+ | `minimax-h3-turbo`, `minimax-h3` | 3–15 s | 768p / 7:4 | Text, first/last frames, or up to 9 image, 3 video, and 3 audio references |
200
+ | `wan3.0` | 2–30 s | 1080p / adaptive | Text, frames, or up to 10 reference images and 5 reference videos |
201
+ | `flux3` | 5–20 s generation; 5–15 s continuation | 720p / 16:9 | Text, frames, or `video edit --video` for continuation |
202
+ | `seedance2.5` | 4–30 s | 720p / 16:9 | Text or `video edit` references, including audio alone |
203
+
204
+ Use repeatable `--reference-image`, `--reference-video`, and `--reference-audio`
205
+ options for MiniMax references. References cannot be combined with first/last
206
+ frames. WAN 3.0 also separates frame and reference inputs. Seedance 2.5 supports
207
+ up to 1080p;
208
+ Seedance 2 and Mini retain their 15-second limit. Grok video model names are
209
+ `grok-imagine` and `grok-imagine-1.5` (image required); `grok` and `grok15`
210
+ remain accepted aliases. The CLI's default video model remains `ltx23`.
211
+
212
+ Topaz upscaling models are `topaz-prob-4`, `topaz-slp-2.5`, and `topaz-ast-2`.
213
+ They support 4K output. The default `seedvr2` supports up to 1440p, with a
214
+ 20-second source limit at 1080p and a 10-second limit at 1440p, inclusive.
215
+
168
216
  ### Lipsync and Audio
169
217
 
170
218
  | Command | Description |
@@ -176,6 +224,8 @@ everypixel video edit \
176
224
  | `everypixel audio tts-clone` | Create speech from a cloned voice sample |
177
225
  | `everypixel audio tts-voice` | Create speech with a character voice |
178
226
 
227
+ Lipsync supports 720p; the API checks source media dimensions and duration.
228
+
179
229
  Examples:
180
230
 
181
231
  ```bash
@@ -0,0 +1 @@
1
+ __version__ = "0.2.1"
@@ -7,9 +7,9 @@ import time
7
7
  from collections.abc import Callable, Mapping
8
8
  from dataclasses import dataclass
9
9
  from pathlib import Path
10
- from typing import Any, Protocol
10
+ from typing import Any, Protocol, get_args
11
11
 
12
- from pydantic import TypeAdapter, ValidationError
12
+ from pydantic import BaseModel, TypeAdapter, ValidationError
13
13
 
14
14
  from ..errors import (
15
15
  APIResponseError,
@@ -36,12 +36,16 @@ from ..schemas import (
36
36
  ImageEditPayload,
37
37
  ImageGeneratePayload,
38
38
  ImageUpscalePayload,
39
+ ImageVectorizePayload,
39
40
  LipsyncImagePayload,
40
41
  LipsyncVideoPayload,
42
+ RecraftGeneratePayload,
41
43
  TaskResponse,
42
44
  VideoEditRequest,
43
45
  VideoGenerateRequest,
46
+ VideoGenerateModel,
44
47
  VideoUpscalePayload,
48
+ normalize_grok_model,
45
49
  )
46
50
  from .models import (
47
51
  ExecutionOptions,
@@ -328,6 +332,8 @@ def fallback_extension_for_endpoint(path: str) -> str:
328
332
  """Infer the existing conservative extension fallback from an endpoint path."""
329
333
 
330
334
  lowered = path.lower()
335
+ if lowered == "/v1/image_vectorize":
336
+ return ".svg"
331
337
  if "transcribe" in lowered or "asr" in lowered:
332
338
  return ".txt"
333
339
  if "video" in lowered or "lipsync" in lowered:
@@ -468,6 +474,31 @@ def _build_wan_video_edit_payload(inputs: _VideoEditInputs) -> dict[str, Any]:
468
474
  )
469
475
 
470
476
 
477
+ def _build_flux3_video_edit_payload(inputs: _VideoEditInputs) -> dict[str, Any]:
478
+ _reject_video_edit_options(
479
+ inputs.model,
480
+ {
481
+ "image": inputs.images,
482
+ "audio": inputs.audio,
483
+ "seed": inputs.seed,
484
+ "keyframe": inputs.keyframes,
485
+ "public_figure_threshold": inputs.public_figure_threshold,
486
+ },
487
+ )
488
+ return _validated_video_edit_payload(
489
+ {
490
+ "prompt": inputs.prompt,
491
+ "model": inputs.model,
492
+ "duration": inputs.duration,
493
+ "resolution": inputs.resolution,
494
+ "aspect_ratio": inputs.aspect_ratio,
495
+ "video_url": media_value(inputs.video) if inputs.video else None,
496
+ "generate_audio": inputs.generate_audio,
497
+ "callback_url": inputs.callback_url,
498
+ }
499
+ )
500
+
501
+
471
502
  def _parse_aleph_keyframe(value: str) -> dict[str, Any]:
472
503
  try:
473
504
  keyframe = json.loads(value)
@@ -509,9 +540,11 @@ def _build_aleph_video_edit_payload(inputs: _VideoEditInputs) -> dict[str, Any]:
509
540
  _VIDEO_EDIT_BUILDERS: dict[str, Callable[[_VideoEditInputs], dict[str, Any]]] = {
510
541
  "seedance2": _build_seedance_video_edit_payload,
511
542
  "seedance2-mini": _build_seedance_video_edit_payload,
543
+ "seedance2.5": _build_seedance_video_edit_payload,
512
544
  "kling-3-omni": _build_kling_video_edit_payload,
513
545
  "wan2.7": _build_wan_video_edit_payload,
514
546
  "aleph2": _build_aleph_video_edit_payload,
547
+ "flux3": _build_flux3_video_edit_payload,
515
548
  }
516
549
 
517
550
 
@@ -557,14 +590,30 @@ def build_video_edit_payload(
557
590
  return builder(inputs)
558
591
 
559
592
 
593
+ _IMAGE_GENERATE_BUILDERS: dict[str, type[BaseModel]] = {
594
+ "recraftv4_1_vector": RecraftGeneratePayload,
595
+ "recraftv4_1_pro_vector": RecraftGeneratePayload,
596
+ }
597
+
598
+
560
599
  def build_image_generate_payload(**values: Any) -> dict[str, Any]:
561
600
  """Validate and build image generation payloads including local media."""
562
601
 
563
602
  image = values.pop("image", None)
564
- return ImageGeneratePayload(
565
- **{key: value for key, value in values.items() if value is not None},
566
- image_url=media_value(image) if image else None,
567
- ).model_dump(exclude_none=True)
603
+ if image is not None:
604
+ values["image_url"] = media_value(image)
605
+ controls = values.get("controls")
606
+ if isinstance(controls, str):
607
+ try:
608
+ values["controls"] = json.loads(controls)
609
+ except json.JSONDecodeError as exc:
610
+ raise InputParsingError("Unable to parse Recraft controls JSON") from exc
611
+ builder = _IMAGE_GENERATE_BUILDERS.get(
612
+ values.get("model", "zimage"), ImageGeneratePayload
613
+ )
614
+ return builder.model_validate(
615
+ {key: value for key, value in values.items() if value is not None}
616
+ ).model_dump(mode="json", exclude_none=True)
568
617
 
569
618
 
570
619
  def build_image_edit_payload(**values: Any) -> dict[str, Any]:
@@ -574,7 +623,15 @@ def build_image_edit_payload(**values: Any) -> dict[str, Any]:
574
623
  return ImageEditPayload(
575
624
  **{key: value for key, value in values.items() if value is not None},
576
625
  image_urls=[media_value(item) for item in images],
577
- ).model_dump(exclude_none=True)
626
+ ).model_dump(mode="json", exclude_none=True)
627
+
628
+
629
+ def build_image_vectorize_payload(
630
+ *, image: str, callback_url: str | None
631
+ ) -> dict[str, Any]:
632
+ return ImageVectorizePayload.model_validate(
633
+ {"image_url": media_value(image), "callback_url": callback_url}
634
+ ).model_dump(mode="json", exclude_none=True)
578
635
 
579
636
 
580
637
  def build_image_upscale_payload(**values: Any) -> dict[str, Any]:
@@ -594,13 +651,16 @@ def build_video_generate_payload(**values: Any) -> dict[str, Any]:
594
651
  last_image = values.pop("last_image", None)
595
652
  reference_images = values.pop("reference_images", [])
596
653
  reference_videos = values.pop("reference_videos", [])
654
+ reference_audios = values.pop("reference_audios", [])
655
+ values = normalize_grok_model(values)
597
656
  if values.get("duration") is None:
598
657
  model = values.get("model")
599
658
  required_duration_defaults = {
600
- "wan22": 5,
659
+ "minimax-h3-turbo": 5,
660
+ "minimax-h3": 5,
601
661
  "ltx23": 5,
602
- "grok": 5,
603
- "grok15": 5,
662
+ "grok-imagine": 5,
663
+ "grok-imagine-1.5": 5,
604
664
  }
605
665
  values["duration"] = (
606
666
  required_duration_defaults.get(model) if isinstance(model, str) else None
@@ -618,6 +678,10 @@ def build_video_generate_payload(**values: Any) -> dict[str, Any]:
618
678
  payload["reference_video_urls"] = [
619
679
  media_value(value) for value in reference_videos
620
680
  ]
681
+ if reference_audios:
682
+ payload["reference_audio_urls"] = [
683
+ media_value(value) for value in reference_audios
684
+ ]
621
685
  validated = _VIDEO_GENERATE_ADAPTER.validate_python(
622
686
  {key: value for key, value in payload.items() if value is not None}
623
687
  )
@@ -771,6 +835,8 @@ class ApplicationServices:
771
835
  seed: int,
772
836
  callback_url: str | None,
773
837
  execution: ExecutionOptions,
838
+ lora_url: str | None = None,
839
+ controls: dict[str, Any] | str | None = None,
774
840
  ) -> OperationResult:
775
841
  payload = build_image_generate_payload(
776
842
  prompt=prompt,
@@ -781,12 +847,33 @@ class ApplicationServices:
781
847
  resolution=resolution,
782
848
  seed=seed,
783
849
  callback_url=callback_url,
850
+ lora_url=lora_url,
851
+ controls=controls,
784
852
  )
785
853
  return self._execute_async(
786
854
  endpoint="/v1/image_generate",
787
855
  payload=payload,
788
856
  execution=execution,
789
- fallback_extension=".png",
857
+ fallback_extension=(
858
+ ".svg" if model in _IMAGE_GENERATE_BUILDERS else ".png"
859
+ ),
860
+ )
861
+
862
+ def execute_image_vectorize(
863
+ self,
864
+ *,
865
+ image: str,
866
+ callback_url: str | None = None,
867
+ execution: ExecutionOptions,
868
+ ) -> OperationResult:
869
+ return self._execute_async(
870
+ endpoint="/v1/image_vectorize",
871
+ payload=build_image_vectorize_payload(
872
+ image=image,
873
+ callback_url=callback_url,
874
+ ),
875
+ execution=execution,
876
+ fallback_extension=".svg",
790
877
  )
791
878
 
792
879
  def execute_image_edit(
@@ -800,6 +887,7 @@ class ApplicationServices:
800
887
  seed: int,
801
888
  callback_url: str | None,
802
889
  execution: ExecutionOptions,
890
+ megapixel_ratio: float = 1.0,
803
891
  ) -> OperationResult:
804
892
  payload = build_image_edit_payload(
805
893
  prompt=prompt,
@@ -807,6 +895,7 @@ class ApplicationServices:
807
895
  model=model,
808
896
  image_size=image_size,
809
897
  resolution=resolution,
898
+ megapixel_ratio=megapixel_ratio,
810
899
  seed=seed,
811
900
  callback_url=callback_url,
812
901
  )
@@ -885,10 +974,9 @@ class ApplicationServices:
885
974
  duration: int | None,
886
975
  resolution: str | None,
887
976
  aspect_ratio: str | None,
888
- lora_high_url: str | None = None,
889
- lora_low_url: str | None = None,
890
977
  reference_images: list[str] | None = None,
891
978
  reference_videos: list[str] | None = None,
979
+ reference_audios: list[str] | None = None,
892
980
  image: str | None = None,
893
981
  last_image: str | None = None,
894
982
  seed: int | None = None,
@@ -902,10 +990,9 @@ class ApplicationServices:
902
990
  duration=duration,
903
991
  resolution=resolution,
904
992
  aspect_ratio=aspect_ratio,
905
- lora_high_url=lora_high_url,
906
- lora_low_url=lora_low_url,
907
993
  reference_images=reference_images or [],
908
994
  reference_videos=reference_videos or [],
995
+ reference_audios=reference_audios or [],
909
996
  image=image,
910
997
  last_image=last_image,
911
998
  seed=seed,
@@ -966,11 +1053,15 @@ class ApplicationServices:
966
1053
  task_id: str | None,
967
1054
  resolution: str,
968
1055
  execution: ExecutionOptions,
1056
+ model: str = "seedvr2",
1057
+ callback_url: str | None = None,
969
1058
  ) -> OperationResult:
970
1059
  payload = build_video_upscale_payload(
971
1060
  video=video,
972
1061
  video_from_task_id=task_id,
973
1062
  resolution=resolution,
1063
+ model=model,
1064
+ callback_url=callback_url,
974
1065
  )
975
1066
  return self._execute_async(
976
1067
  endpoint="/v1/video_upscale",
@@ -1169,8 +1260,10 @@ class ApplicationServices:
1169
1260
  path = operation.path
1170
1261
  resolved_method = operation.method
1171
1262
  resolved_method = resolved_method or "POST"
1172
- if path == "/v1/video_edit":
1263
+ if path in {"/v1/video_edit", "/v1/video_generate"}:
1173
1264
  normalize_video_edit_content(payload)
1265
+ if path == "/v1/image_vectorize" and isinstance(payload.get("image_url"), str):
1266
+ payload["image_url"] = media_value(payload["image_url"])
1174
1267
  if help_schema:
1175
1268
  return OperationResult(
1176
1269
  value=operation_help(
@@ -1181,6 +1274,14 @@ class ApplicationServices:
1181
1274
  schema_source=schema_source,
1182
1275
  )
1183
1276
  )
1277
+ if path in {"/v1/image_generate", "/v1/image_edit", "/v1/video_generate"}:
1278
+ payload = normalize_grok_model(payload)
1279
+ if path == "/v1/video_generate" and payload.get("model") in get_args(
1280
+ VideoGenerateModel
1281
+ ):
1282
+ _VIDEO_GENERATE_ADAPTER.validate_python(payload, extra="ignore")
1283
+ if path == "/v1/image_vectorize":
1284
+ ImageVectorizePayload.model_validate(payload)
1184
1285
  if path == "/v1/video_edit":
1185
1286
  # Generic callers may send forward-compatible fields that the local
1186
1287
  # specialized model does not know yet; validate known semantics
@@ -1205,7 +1306,12 @@ class ApplicationServices:
1205
1306
  method=resolved_method,
1206
1307
  payload=payload,
1207
1308
  execution=execution,
1208
- fallback_extension=fallback_extension_for_endpoint(path),
1309
+ fallback_extension=(
1310
+ ".svg"
1311
+ if path == "/v1/image_generate"
1312
+ and payload.get("model") in _IMAGE_GENERATE_BUILDERS
1313
+ else fallback_extension_for_endpoint(path)
1314
+ ),
1209
1315
  )
1210
1316
  )
1211
1317
 
@@ -1322,17 +1428,19 @@ def parse_generic_payload(
1322
1428
 
1323
1429
 
1324
1430
  def normalize_video_edit_content(payload: dict[str, Any]) -> None:
1325
- """Encode local generic ``video_edit`` references as data URIs."""
1326
-
1327
- video_url = payload.get("video_url")
1328
- if isinstance(video_url, str):
1329
- payload["video_url"] = media_value(video_url)
1330
- reference_image_urls = payload.get("reference_image_urls")
1331
- if isinstance(reference_image_urls, list):
1332
- payload["reference_image_urls"] = [
1333
- media_value(value) if isinstance(value, str) else value
1334
- for value in reference_image_urls
1335
- ]
1431
+ """Encode local generic video frames and references as data URIs."""
1432
+
1433
+ for key in ("video_url", "image_url", "image_last_url"):
1434
+ value = payload.get(key)
1435
+ if isinstance(value, str):
1436
+ payload[key] = media_value(value)
1437
+ for key in ("reference_image_urls", "reference_video_urls", "reference_audio_urls"):
1438
+ values = payload.get(key)
1439
+ if isinstance(values, list):
1440
+ payload[key] = [
1441
+ media_value(value) if isinstance(value, str) else value
1442
+ for value in values
1443
+ ]
1336
1444
  keyframes = payload.get("keyframes")
1337
1445
  if isinstance(keyframes, list):
1338
1446
  for keyframe in keyframes: