climage 0.5.0 → 0.5.2

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 CHANGED
@@ -23,13 +23,14 @@ Set one of:
23
23
 
24
24
  **Models:**
25
25
 
26
- | Model | Alias | Description |
27
- | ------------------------------- | ----------------- | ------------------------------------------------------------------------------------- |
28
- | `gemini-3-pro-image-preview` | `nano-banana-pro` | **Default.** State-of-the-art, professional asset production, up to 4K, thinking mode |
29
- | `gemini-2.5-flash-image` | `nano-banana` | Fast & efficient, optimized for high-volume tasks |
30
- | `imagen-4.0-generate-001` | - | Imagen 4 Standard |
31
- | `imagen-4.0-ultra-generate-001` | - | Imagen 4 Ultra (best quality) |
32
- | `imagen-4.0-fast-generate-001` | - | Imagen 4 Fast |
26
+ | Model | Alias | Description |
27
+ | -------------------------------- | ----------------- | ------------------------------------------------------------------------------------- |
28
+ | `gemini-3-pro-image-preview` | `nano-banana-pro` | **Default.** State-of-the-art, professional asset production, up to 4K, thinking mode |
29
+ | `gemini-3.1-flash-image-preview` | `nano-banana-2` | Nano Banana 2 fast native image generation |
30
+ | `gemini-2.5-flash-image` | `nano-banana` | Fast & efficient, optimized for high-volume tasks |
31
+ | `imagen-4.0-generate-001` | - | Imagen 4 Standard |
32
+ | `imagen-4.0-ultra-generate-001` | - | Imagen 4 Ultra (best quality) |
33
+ | `imagen-4.0-fast-generate-001` | - | Imagen 4 Fast |
33
34
 
34
35
  Example:
35
36
 
package/dist/cli.js CHANGED
@@ -653,6 +653,9 @@ var falProvider = {
653
653
 
654
654
  // src/providers/google.ts
655
655
  import { GoogleGenAI } from "@google/genai";
656
+ import { mkdtemp, readFile, rm } from "fs/promises";
657
+ import { tmpdir } from "os";
658
+ import { join } from "path";
656
659
  function getGeminiApiKey(env) {
657
660
  return env.GEMINI_API_KEY || env.GOOGLE_API_KEY || env.GOOGLE_GENAI_API_KEY;
658
661
  }
@@ -674,6 +677,7 @@ function log3(...args) {
674
677
  var MODEL_ALIASES = {
675
678
  "nano-banana": "gemini-2.5-flash-image",
676
679
  "nano-banana-pro": "gemini-3-pro-image-preview",
680
+ "nano-banana-2": "gemini-3.1-flash-image-preview",
677
681
  // Veo (video)
678
682
  veo2: "veo-2.0-generate-001",
679
683
  "veo-2": "veo-2.0-generate-001",
@@ -700,7 +704,26 @@ function imageToGoogleFormat(imageInput) {
700
704
  }
701
705
  return { fileUri: imageInput };
702
706
  }
703
- var GEMINI_IMAGE_MODELS = ["gemini-2.5-flash-image", "gemini-3-pro-image-preview"];
707
+ function imageToVeoFormat(imageInput) {
708
+ if (imageInput.startsWith("data:")) {
709
+ const parsed = parseDataUri(imageInput);
710
+ if (!parsed?.data) {
711
+ throw new Error("Failed to parse data URI for Veo image input");
712
+ }
713
+ return { imageBytes: parsed.data, mimeType: parsed.mimeType };
714
+ }
715
+ if (imageInput.startsWith("gs://")) {
716
+ return { gcsUri: imageInput };
717
+ }
718
+ throw new Error(
719
+ `Veo image inputs must be data: URIs or gs:// URIs (got ${imageInput.slice(0, 24)}...)`
720
+ );
721
+ }
722
+ var GEMINI_IMAGE_MODELS = [
723
+ "gemini-2.5-flash-image",
724
+ "gemini-3-pro-image-preview",
725
+ "gemini-3.1-flash-image-preview"
726
+ ];
704
727
  function resolveModel(model) {
705
728
  if (!model) return "gemini-2.5-flash-image";
706
729
  return MODEL_ALIASES[model] ?? model;
@@ -718,6 +741,31 @@ async function downloadBytes3(url) {
718
741
  log3(`Downloaded ${ab.byteLength} bytes in ${Date.now() - start}ms, type: ${ct}`);
719
742
  return { bytes: new Uint8Array(ab), mimeType: ct };
720
743
  }
744
+ async function downloadGeneratedVideo(ai, generatedVideo) {
745
+ const video = generatedVideo?.video;
746
+ if (video?.videoBytes) {
747
+ return {
748
+ bytes: new Uint8Array(Buffer.from(video.videoBytes, "base64")),
749
+ mimeType: video.mimeType
750
+ };
751
+ }
752
+ if (video?.uri && !video.uri.startsWith("gs://")) {
753
+ try {
754
+ return await downloadBytes3(video.uri);
755
+ } catch (err) {
756
+ log3("Direct video download failed, falling back to ai.files.download:", String(err));
757
+ }
758
+ }
759
+ const tempDir = await mkdtemp(join(tmpdir(), "climage-veo-"));
760
+ const downloadPath = join(tempDir, "video.mp4");
761
+ try {
762
+ await ai.files.download({ file: generatedVideo, downloadPath });
763
+ const buf = await readFile(downloadPath);
764
+ return { bytes: new Uint8Array(buf), mimeType: video?.mimeType ?? "video/mp4" };
765
+ } finally {
766
+ await rm(tempDir, { recursive: true, force: true });
767
+ }
768
+ }
721
769
  async function sleep2(ms) {
722
770
  await new Promise((r) => setTimeout(r, ms));
723
771
  }
@@ -783,15 +831,15 @@ async function generateWithVeo(ai, model, req) {
783
831
  const config = {
784
832
  numberOfVideos: req.n,
785
833
  ...req.aspectRatio ? { aspectRatio: req.aspectRatio } : {},
786
- // Add duration if specified (Veo 3.1 supports 4, 6, 8)
787
- ...req.duration !== void 0 ? { durationSeconds: String(req.duration) } : {}
834
+ // Add duration if specified (Veo supports 4-8 seconds depending on model)
835
+ ...req.duration !== void 0 ? { durationSeconds: req.duration } : {}
788
836
  };
789
837
  if (req.inputImages?.length && isVeo31Model(model)) {
790
838
  const referenceImages = req.inputImages.slice(0, 3).map((img) => {
791
- const imageData = imageToGoogleFormat(img);
839
+ const imageData = imageToVeoFormat(img);
792
840
  return {
793
841
  image: imageData,
794
- referenceType: "asset"
842
+ referenceType: "ASSET"
795
843
  };
796
844
  });
797
845
  config.referenceImages = referenceImages;
@@ -804,12 +852,12 @@ async function generateWithVeo(ai, model, req) {
804
852
  };
805
853
  const firstFrameImage = req.startFrame ?? (req.inputImages?.length === 1 ? req.inputImages[0] : void 0);
806
854
  if (firstFrameImage && isVeo31Model(model)) {
807
- const imageData = imageToGoogleFormat(firstFrameImage);
855
+ const imageData = imageToVeoFormat(firstFrameImage);
808
856
  generateParams.image = imageData;
809
857
  log3("Added first frame image");
810
858
  }
811
859
  if (req.endFrame && isVeo31Model(model)) {
812
- const lastFrameData = imageToGoogleFormat(req.endFrame);
860
+ const lastFrameData = imageToVeoFormat(req.endFrame);
813
861
  config.lastFrame = lastFrameData;
814
862
  log3("Added last frame for interpolation");
815
863
  }
@@ -839,26 +887,22 @@ async function generateWithVeo(ai, model, req) {
839
887
  for (let i = 0; i < Math.min(videos.length, req.n); i++) {
840
888
  const v = videos[i];
841
889
  log3(`Processing video ${i}:`, JSON.stringify(v).slice(0, 300));
842
- const uri = v?.video?.uri;
843
- if (!uri) {
844
- log3(`Video ${i} has no URI, skipping`);
890
+ if (!v?.video) {
891
+ log3(`Video ${i} has no video payload, skipping`);
845
892
  continue;
846
893
  }
847
- if (uri.startsWith("gs://")) {
848
- throw new Error(
849
- `Google Veo returned a gs:// URI (${uri}). Configure outputGcsUri / Vertex flow to fetch from GCS.`
850
- );
851
- }
852
- const { bytes, mimeType } = await downloadBytes3(uri);
853
- out.push({
894
+ const uri = v?.video?.uri;
895
+ const { bytes, mimeType } = await downloadGeneratedVideo(ai, v);
896
+ const item = {
854
897
  kind: "video",
855
898
  provider: "google",
856
899
  model,
857
900
  index: i,
858
- url: uri,
859
901
  bytes,
860
902
  ...mimeType !== void 0 ? { mimeType } : {}
861
- });
903
+ };
904
+ if (uri) item.url = uri;
905
+ out.push(item);
862
906
  }
863
907
  if (!out.length) throw new Error("Google Veo returned videos but none were downloadable");
864
908
  log3(`Successfully generated ${out.length} video(s)`);