noosphere 0.8.0 → 0.9.0

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
@@ -7,6 +7,8 @@ One import. Every model. Every modality.
7
7
  ## Features
8
8
 
9
9
  - **7 modalities** — LLM, image, video, TTS, STT, music, and embeddings
10
+ - **OpenAI media** — GPT-Image-1/1.5, DALL-E 2/3, Sora 2/Pro (video), TTS-1/HD, Whisper — all auto-fetched from `OPENAI_API_KEY`
11
+ - **Google media** — Imagen 4.0 (image), Veo 2/3/3.1 (video), Gemini TTS — all auto-fetched from `GEMINI_API_KEY`
10
12
  - **Always up-to-date models** — Dynamic auto-fetch from ALL provider APIs at runtime (OpenAI, Anthropic, Google, Groq, Mistral, xAI, Cerebras, OpenRouter)
11
13
  - **Dynamic descriptions** — Model descriptions fetched from source (Ollama library, HuggingFace READMEs, CivitAI API) — no hardcoded strings
12
14
  - **Modality-filtered sync** — `syncModels('llm')` only fetches LLM providers, avoiding unnecessary requests
@@ -38,13 +40,29 @@ const response = await ai.chat({
38
40
  });
39
41
  console.log(response.content);
40
42
 
41
- // Generate an image
43
+ // Generate an image with GPT-Image-1 (OpenAI) — just needs OPENAI_API_KEY
42
44
  const image = await ai.image({
43
45
  prompt: 'A sunset over mountains',
46
+ provider: 'openai-media',
47
+ });
48
+ // image.buffer contains the PNG data
49
+
50
+ // Generate an image with Imagen 4.0 (Google) — just needs GEMINI_API_KEY
51
+ const googleImage = await ai.image({
52
+ prompt: 'A sunset over mountains',
53
+ provider: 'google-media',
54
+ });
55
+ // googleImage.buffer contains the PNG data
56
+
57
+ // Generate an image with DALL-E 3
58
+ const dalle = await ai.image({
59
+ prompt: 'A sunset over mountains',
60
+ provider: 'openai-media',
61
+ model: 'dall-e-3',
44
62
  width: 1024,
45
63
  height: 1024,
46
64
  });
47
- console.log(image.url);
65
+ console.log(dalle.url);
48
66
 
49
67
  // Generate a video
50
68
  const video = await ai.video({
@@ -53,7 +71,7 @@ const video = await ai.video({
53
71
  });
54
72
  console.log(video.url);
55
73
 
56
- // Text-to-speech
74
+ // Text-to-speech with OpenAI TTS — just needs OPENAI_API_KEY
57
75
  const audio = await ai.speak({
58
76
  text: 'Welcome to Noosphere',
59
77
  voice: 'alloy',
@@ -368,6 +386,8 @@ await ai.uninstallModel('deepseek-r1:14b');
368
386
  | Provider | Modality | Models | Source | Auto-Detect |
369
387
  |---|---|---|---|---|
370
388
  | **pi-ai** | LLM | 482 | OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter, Cerebras | API keys |
389
+ | **openai-media** | image, video, tts, stt | 12 | GPT-Image-1/1.5, DALL-E 2/3, Sora 2/Pro, TTS-1/HD, Whisper | `OPENAI_API_KEY` |
390
+ | **google-media** | image, video, tts | 10 | Imagen 4.0, Veo 2/3/3.1, Gemini TTS (Flash/Pro) | `GEMINI_API_KEY` |
371
391
  | **ollama** | LLM, embedding | 70 | 38 installed + 32 from Ollama web catalog | `localhost:11434` |
372
392
  | **hf-local** | image, video, tts, stt, music | 220 | HuggingFace catalog (FLUX, SDXL, Wan2.2, Whisper, MusicGen) | Always (no API key) |
373
393
  | **huggingface** | LLM, image, tts | dynamic | HuggingFace Inference API | `HUGGINGFACE_TOKEN` |
@@ -400,11 +420,11 @@ await ai.syncModels();
400
420
 
401
421
  | Modality | Providers Synced |
402
422
  |---|---|
403
- | `llm` | pi-ai, ollama, openai-compat, huggingface (cloud, needs API key) |
404
- | `image` | hf-local, comfyui, fal, huggingface (cloud) |
405
- | `video` | hf-local, comfyui, fal |
406
- | `tts` | hf-local (speech models), fal, piper, kokoro, huggingface (cloud) |
407
- | `stt` | hf-local, whisper-local |
423
+ | `llm` | pi-ai, ollama, openai-compat, huggingface (cloud) |
424
+ | `image` | **openai-media** (GPT-Image-1, DALL-E), **google-media** (Imagen 4.0), hf-local, comfyui, fal, huggingface (cloud) |
425
+ | `video` | **openai-media** (Sora 2/Pro), **google-media** (Veo 2/3/3.1), hf-local, comfyui, fal |
426
+ | `tts` | **openai-media** (TTS-1, TTS-1-HD), **google-media** (Gemini TTS), hf-local, fal, piper, kokoro, huggingface (cloud) |
427
+ | `stt` | **openai-media** (Whisper), hf-local, whisper-local |
408
428
  | `music` | hf-local (MusicGen, AudioLDM, etc.), audiocraft |
409
429
  | `embedding` | ollama |
410
430
 
package/dist/index.cjs CHANGED
@@ -21,11 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AudioCraftProvider: () => AudioCraftProvider,
24
+ GoogleMediaProvider: () => GoogleMediaProvider,
24
25
  HfLocalProvider: () => HfLocalProvider,
25
26
  Noosphere: () => Noosphere,
26
27
  NoosphereError: () => NoosphereError,
27
28
  OllamaProvider: () => OllamaProvider,
28
29
  OpenAICompatProvider: () => OpenAICompatProvider,
30
+ OpenAIMediaProvider: () => OpenAIMediaProvider,
29
31
  PROVIDER_IDS: () => PROVIDER_IDS,
30
32
  PROVIDER_LOGOS: () => PROVIDER_LOGOS,
31
33
  WhisperLocalProvider: () => WhisperLocalProvider,
@@ -2538,6 +2540,501 @@ async function detectOpenAICompatServers() {
2538
2540
  return providers;
2539
2541
  }
2540
2542
 
2543
+ // src/providers/openai-media.ts
2544
+ var OPENAI_API_BASE = "https://api.openai.com/v1";
2545
+ var FETCH_TIMEOUT_MS5 = 8e3;
2546
+ var MODEL_PREFIX_MAP = [
2547
+ { prefix: "dall-e-", modality: "image" },
2548
+ { prefix: "gpt-image-", modality: "image" },
2549
+ { prefix: "sora-", modality: "video" },
2550
+ { prefix: "tts-", modality: "tts" },
2551
+ { prefix: "whisper-", modality: "stt" }
2552
+ ];
2553
+ function classifyModel(id) {
2554
+ for (const { prefix, modality } of MODEL_PREFIX_MAP) {
2555
+ if (id.startsWith(prefix)) return modality;
2556
+ }
2557
+ return null;
2558
+ }
2559
+ var OpenAIMediaProvider = class {
2560
+ constructor(apiKey) {
2561
+ this.apiKey = apiKey;
2562
+ }
2563
+ id = "openai-media";
2564
+ name = "OpenAI (Image, Video, TTS, STT)";
2565
+ modalities = ["image", "video", "tts", "stt"];
2566
+ isLocal = false;
2567
+ modelsCache = null;
2568
+ async ping() {
2569
+ try {
2570
+ const controller = new AbortController();
2571
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS5);
2572
+ try {
2573
+ const res = await fetch(`${OPENAI_API_BASE}/models`, {
2574
+ headers: { Authorization: `Bearer ${this.apiKey}` },
2575
+ signal: controller.signal
2576
+ });
2577
+ return res.ok;
2578
+ } finally {
2579
+ clearTimeout(timer);
2580
+ }
2581
+ } catch {
2582
+ return false;
2583
+ }
2584
+ }
2585
+ async listModels(modality) {
2586
+ if (this.modelsCache) {
2587
+ return modality ? this.modelsCache.filter((m) => m.modality === modality) : this.modelsCache;
2588
+ }
2589
+ try {
2590
+ const controller = new AbortController();
2591
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS5);
2592
+ let data;
2593
+ try {
2594
+ const res = await fetch(`${OPENAI_API_BASE}/models`, {
2595
+ headers: { Authorization: `Bearer ${this.apiKey}` },
2596
+ signal: controller.signal
2597
+ });
2598
+ if (!res.ok) return [];
2599
+ data = await res.json();
2600
+ } finally {
2601
+ clearTimeout(timer);
2602
+ }
2603
+ const entries = data?.data ?? [];
2604
+ const logo = getProviderLogo("openai");
2605
+ const models = [];
2606
+ for (const entry of entries) {
2607
+ const mod = classifyModel(entry.id);
2608
+ if (!mod) continue;
2609
+ const info = {
2610
+ id: entry.id,
2611
+ provider: "openai-media",
2612
+ name: entry.id,
2613
+ modality: mod,
2614
+ local: false,
2615
+ cost: { price: 0, unit: "per_request" },
2616
+ logo,
2617
+ description: entry.description,
2618
+ capabilities: this.getCapabilities(entry.id, mod)
2619
+ };
2620
+ models.push(info);
2621
+ }
2622
+ this.modelsCache = models;
2623
+ return modality ? models.filter((m) => m.modality === modality) : models;
2624
+ } catch {
2625
+ return [];
2626
+ }
2627
+ }
2628
+ async image(options) {
2629
+ const model = options.model ?? "gpt-image-1";
2630
+ const width = options.width ?? 1024;
2631
+ const height = options.height ?? 1024;
2632
+ const start = Date.now();
2633
+ const isGptImage = model.startsWith("gpt-image-");
2634
+ const body = {
2635
+ model,
2636
+ prompt: options.prompt,
2637
+ n: 1,
2638
+ size: `${width}x${height}`
2639
+ };
2640
+ if (!isGptImage) {
2641
+ body.response_format = "url";
2642
+ }
2643
+ const res = await fetch(`${OPENAI_API_BASE}/images/generations`, {
2644
+ method: "POST",
2645
+ headers: {
2646
+ "Content-Type": "application/json",
2647
+ Authorization: `Bearer ${this.apiKey}`
2648
+ },
2649
+ body: JSON.stringify(body)
2650
+ });
2651
+ if (!res.ok) {
2652
+ const errorBody = await res.text();
2653
+ throw new Error(`OpenAI image generation failed (${res.status}): ${errorBody}`);
2654
+ }
2655
+ const data = await res.json();
2656
+ const item = data?.data?.[0];
2657
+ const result = {
2658
+ provider: "openai-media",
2659
+ model,
2660
+ modality: "image",
2661
+ latencyMs: Date.now() - start,
2662
+ usage: {
2663
+ cost: 0,
2664
+ unit: "per_image"
2665
+ },
2666
+ media: {
2667
+ width,
2668
+ height,
2669
+ format: "png"
2670
+ }
2671
+ };
2672
+ if (item?.b64_json) {
2673
+ result.buffer = Buffer.from(item.b64_json, "base64");
2674
+ } else if (item?.url) {
2675
+ result.url = item.url;
2676
+ }
2677
+ return result;
2678
+ }
2679
+ async speak(options) {
2680
+ const model = options.model ?? "tts-1";
2681
+ const voice = options.voice ?? "alloy";
2682
+ const format = options.format ?? "mp3";
2683
+ const speed = options.speed ?? 1;
2684
+ const start = Date.now();
2685
+ const body = {
2686
+ model,
2687
+ input: options.text,
2688
+ voice,
2689
+ response_format: format,
2690
+ speed
2691
+ };
2692
+ const res = await fetch(`${OPENAI_API_BASE}/audio/speech`, {
2693
+ method: "POST",
2694
+ headers: {
2695
+ "Content-Type": "application/json",
2696
+ Authorization: `Bearer ${this.apiKey}`
2697
+ },
2698
+ body: JSON.stringify(body)
2699
+ });
2700
+ if (!res.ok) {
2701
+ const errorBody = await res.text();
2702
+ throw new Error(`OpenAI TTS failed (${res.status}): ${errorBody}`);
2703
+ }
2704
+ const arrayBuffer = await res.arrayBuffer();
2705
+ const buffer = Buffer.from(arrayBuffer);
2706
+ return {
2707
+ buffer,
2708
+ provider: "openai-media",
2709
+ model,
2710
+ modality: "tts",
2711
+ latencyMs: Date.now() - start,
2712
+ usage: {
2713
+ cost: 0,
2714
+ input: options.text.length,
2715
+ unit: "per_1k_chars"
2716
+ },
2717
+ media: {
2718
+ format
2719
+ }
2720
+ };
2721
+ }
2722
+ async video(options) {
2723
+ const model = options.model ?? "sora-2";
2724
+ const start = Date.now();
2725
+ const body = {
2726
+ model,
2727
+ prompt: options.prompt,
2728
+ n: 1
2729
+ };
2730
+ if (options.duration) body.duration = options.duration;
2731
+ if (options.width && options.height) body.size = `${options.width}x${options.height}`;
2732
+ const res = await fetch(`${OPENAI_API_BASE}/videos/generations`, {
2733
+ method: "POST",
2734
+ headers: {
2735
+ "Content-Type": "application/json",
2736
+ Authorization: `Bearer ${this.apiKey}`
2737
+ },
2738
+ body: JSON.stringify(body)
2739
+ });
2740
+ if (!res.ok) {
2741
+ const errorBody = await res.text();
2742
+ throw new Error(`OpenAI video generation failed (${res.status}): ${errorBody}`);
2743
+ }
2744
+ const data = await res.json();
2745
+ const videoUrl = data?.data?.[0]?.url;
2746
+ return {
2747
+ url: videoUrl,
2748
+ provider: "openai-media",
2749
+ model,
2750
+ modality: "video",
2751
+ latencyMs: Date.now() - start,
2752
+ usage: {
2753
+ cost: 0,
2754
+ unit: "per_video"
2755
+ },
2756
+ media: {
2757
+ duration: options.duration,
2758
+ width: options.width,
2759
+ height: options.height
2760
+ }
2761
+ };
2762
+ }
2763
+ getCapabilities(id, modality) {
2764
+ if (modality === "image") {
2765
+ return {
2766
+ maxWidth: id.startsWith("dall-e-3") ? 1792 : 1024,
2767
+ maxHeight: id.startsWith("dall-e-3") ? 1792 : 1024
2768
+ };
2769
+ }
2770
+ if (modality === "tts") {
2771
+ return {
2772
+ voices: ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"]
2773
+ };
2774
+ }
2775
+ if (modality === "video") {
2776
+ return {
2777
+ maxDuration: id.includes("pro") ? 20 : 10,
2778
+ supportsStreaming: false
2779
+ };
2780
+ }
2781
+ if (modality === "stt") {
2782
+ return {
2783
+ languages: ["en", "zh", "de", "es", "ru", "ko", "fr", "ja", "pt", "tr", "pl", "ca", "nl", "ar", "sv", "it", "id", "hi", "fi", "vi", "he", "uk", "el", "ms", "cs", "ro", "da", "hu", "ta", "no", "th", "ur", "hr", "bg", "lt", "la", "mi", "ml", "cy", "sk", "te", "fa", "lv", "bn", "sr", "az", "sl", "kn", "et", "mk", "br", "eu", "is", "hy", "ne", "mn", "bs", "kk", "sq", "sw", "gl", "mr", "pa", "si", "km", "sn", "yo", "so", "af", "oc", "ka", "be", "tg", "sd", "gu", "am", "yi", "lo", "uz", "fo", "ht", "ps", "tk", "nn", "mt", "sa", "lb", "my", "bo", "tl", "mg", "as", "tt", "haw", "ln", "ha", "ba", "jw", "su"]
2784
+ };
2785
+ }
2786
+ return void 0;
2787
+ }
2788
+ };
2789
+
2790
+ // src/providers/google-media.ts
2791
+ var GOOGLE_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
2792
+ var FETCH_TIMEOUT_MS6 = 8e3;
2793
+ var GOOGLE_TTS_VOICES = [
2794
+ "Aoede",
2795
+ "Charon",
2796
+ "Fenrir",
2797
+ "Kore",
2798
+ "Puck",
2799
+ "Leda",
2800
+ "Orus",
2801
+ "Perseus",
2802
+ "Zephyr",
2803
+ "Callirrhoe"
2804
+ ];
2805
+ function classifyGoogleModel(model) {
2806
+ const name = (model.name ?? "").replace("models/", "");
2807
+ const methods = model.supportedGenerationMethods ?? [];
2808
+ if (name.startsWith("imagen") && methods.includes("predict")) return "image";
2809
+ if (name.startsWith("veo") && methods.includes("predictLongRunning")) return "video";
2810
+ if (name.includes("-tts") && methods.includes("generateContent")) return "tts";
2811
+ return null;
2812
+ }
2813
+ var GoogleMediaProvider = class {
2814
+ constructor(apiKey) {
2815
+ this.apiKey = apiKey;
2816
+ }
2817
+ id = "google-media";
2818
+ name = "Google (Image, Video, TTS)";
2819
+ modalities = ["image", "video", "tts"];
2820
+ isLocal = false;
2821
+ modelsCache = null;
2822
+ async ping() {
2823
+ try {
2824
+ const controller = new AbortController();
2825
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS6);
2826
+ try {
2827
+ const res = await fetch(`${GOOGLE_API_BASE}/models?key=${this.apiKey}`, {
2828
+ signal: controller.signal
2829
+ });
2830
+ return res.ok;
2831
+ } finally {
2832
+ clearTimeout(timer);
2833
+ }
2834
+ } catch {
2835
+ return false;
2836
+ }
2837
+ }
2838
+ async listModels(modality) {
2839
+ if (this.modelsCache) {
2840
+ return modality ? this.modelsCache.filter((m) => m.modality === modality) : this.modelsCache;
2841
+ }
2842
+ try {
2843
+ const controller = new AbortController();
2844
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS6);
2845
+ let data;
2846
+ try {
2847
+ const res = await fetch(`${GOOGLE_API_BASE}/models?key=${this.apiKey}`, {
2848
+ signal: controller.signal
2849
+ });
2850
+ if (!res.ok) return [];
2851
+ data = await res.json();
2852
+ } finally {
2853
+ clearTimeout(timer);
2854
+ }
2855
+ const entries = data?.models ?? [];
2856
+ const logo = getProviderLogo("google");
2857
+ const models = [];
2858
+ for (const entry of entries) {
2859
+ const modality2 = classifyGoogleModel(entry);
2860
+ if (!modality2) continue;
2861
+ const fullName = entry.name ?? "";
2862
+ const modelId = fullName.startsWith("models/") ? fullName.slice("models/".length) : fullName;
2863
+ const info = {
2864
+ id: modelId,
2865
+ provider: "google-media",
2866
+ name: entry.displayName ?? modelId,
2867
+ modality: modality2,
2868
+ local: false,
2869
+ cost: { price: 0, unit: modality2 === "video" ? "per_video" : "per_image" },
2870
+ logo,
2871
+ description: entry.description,
2872
+ capabilities: modality2 === "video" ? { maxDuration: 8, supportsStreaming: false } : modality2 === "tts" ? { voices: GOOGLE_TTS_VOICES } : void 0
2873
+ };
2874
+ models.push(info);
2875
+ }
2876
+ this.modelsCache = models;
2877
+ return modality ? models.filter((m) => m.modality === modality) : models;
2878
+ } catch {
2879
+ return [];
2880
+ }
2881
+ }
2882
+ async image(options) {
2883
+ const model = options.model ?? "imagen-4.0-generate-001";
2884
+ const start = Date.now();
2885
+ const body = {
2886
+ instances: [{ prompt: options.prompt }],
2887
+ parameters: {
2888
+ sampleCount: 1
2889
+ }
2890
+ };
2891
+ const res = await fetch(
2892
+ `${GOOGLE_API_BASE}/models/${model}:predict?key=${this.apiKey}`,
2893
+ {
2894
+ method: "POST",
2895
+ headers: { "Content-Type": "application/json" },
2896
+ body: JSON.stringify(body)
2897
+ }
2898
+ );
2899
+ if (!res.ok) {
2900
+ const errorBody = await res.text();
2901
+ throw new Error(`Google image generation failed (${res.status}): ${errorBody}`);
2902
+ }
2903
+ const data = await res.json();
2904
+ const base64 = data?.predictions?.[0]?.bytesBase64Encoded ?? data?.generatedImages?.[0]?.image?.imageBytes;
2905
+ if (!base64) {
2906
+ throw new Error("Google image generation returned no image data");
2907
+ }
2908
+ const buffer = Buffer.from(base64, "base64");
2909
+ return {
2910
+ buffer,
2911
+ provider: "google-media",
2912
+ model,
2913
+ modality: "image",
2914
+ latencyMs: Date.now() - start,
2915
+ usage: {
2916
+ cost: 0,
2917
+ unit: "per_image"
2918
+ },
2919
+ media: {
2920
+ format: "png"
2921
+ }
2922
+ };
2923
+ }
2924
+ async speak(options) {
2925
+ const model = options.model ?? "gemini-2.5-flash-preview-tts";
2926
+ const voice = options.voice ?? "Kore";
2927
+ const start = Date.now();
2928
+ const body = {
2929
+ contents: [{ parts: [{ text: options.text }] }],
2930
+ generationConfig: {
2931
+ response_modalities: ["AUDIO"],
2932
+ speech_config: {
2933
+ voiceConfig: {
2934
+ prebuiltVoiceConfig: { voiceName: voice }
2935
+ }
2936
+ }
2937
+ }
2938
+ };
2939
+ const res = await fetch(
2940
+ `${GOOGLE_API_BASE}/models/${model}:generateContent?key=${this.apiKey}`,
2941
+ {
2942
+ method: "POST",
2943
+ headers: { "Content-Type": "application/json" },
2944
+ body: JSON.stringify(body)
2945
+ }
2946
+ );
2947
+ if (!res.ok) {
2948
+ const errorBody = await res.text();
2949
+ throw new Error(`Google TTS failed (${res.status}): ${errorBody}`);
2950
+ }
2951
+ const data = await res.json();
2952
+ const inlineData = data?.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2953
+ if (!inlineData?.data) {
2954
+ throw new Error("Google TTS returned no audio data");
2955
+ }
2956
+ const buffer = Buffer.from(inlineData.data, "base64");
2957
+ return {
2958
+ buffer,
2959
+ provider: "google-media",
2960
+ model,
2961
+ modality: "tts",
2962
+ latencyMs: Date.now() - start,
2963
+ usage: {
2964
+ cost: 0,
2965
+ input: options.text.length,
2966
+ unit: "per_1k_chars"
2967
+ },
2968
+ media: {
2969
+ format: "wav"
2970
+ // Google returns PCM L16, essentially WAV
2971
+ }
2972
+ };
2973
+ }
2974
+ async video(options) {
2975
+ const model = options.model ?? "veo-3.0-generate-001";
2976
+ const start = Date.now();
2977
+ const body = {
2978
+ instances: [{ prompt: options.prompt }],
2979
+ parameters: {
2980
+ sampleCount: 1
2981
+ }
2982
+ };
2983
+ if (options.duration) body.parameters.durationSeconds = options.duration;
2984
+ const res = await fetch(
2985
+ `${GOOGLE_API_BASE}/models/${model}:predictLongRunning?key=${this.apiKey}`,
2986
+ {
2987
+ method: "POST",
2988
+ headers: { "Content-Type": "application/json" },
2989
+ body: JSON.stringify(body)
2990
+ }
2991
+ );
2992
+ if (!res.ok) {
2993
+ const errorBody = await res.text();
2994
+ throw new Error(`Google video generation failed (${res.status}): ${errorBody}`);
2995
+ }
2996
+ const operation = await res.json();
2997
+ const operationName = operation?.name;
2998
+ if (!operationName) {
2999
+ throw new Error("Google video generation returned no operation name");
3000
+ }
3001
+ const deadline = Date.now() + 3e5;
3002
+ while (Date.now() < deadline) {
3003
+ await new Promise((r) => setTimeout(r, 5e3));
3004
+ const pollRes = await fetch(
3005
+ `${GOOGLE_API_BASE}/${operationName}?key=${this.apiKey}`
3006
+ );
3007
+ if (!pollRes.ok) continue;
3008
+ const status = await pollRes.json();
3009
+ if (status.done) {
3010
+ const videoBase64 = status.response?.generatedSamples?.[0]?.video?.bytesBase64Encoded;
3011
+ if (videoBase64) {
3012
+ return {
3013
+ buffer: Buffer.from(videoBase64, "base64"),
3014
+ provider: "google-media",
3015
+ model,
3016
+ modality: "video",
3017
+ latencyMs: Date.now() - start,
3018
+ usage: { cost: 0, unit: "per_video" },
3019
+ media: { format: "mp4", duration: options.duration }
3020
+ };
3021
+ }
3022
+ const videoUrl = status.response?.generatedSamples?.[0]?.video?.uri;
3023
+ return {
3024
+ url: videoUrl,
3025
+ provider: "google-media",
3026
+ model,
3027
+ modality: "video",
3028
+ latencyMs: Date.now() - start,
3029
+ usage: { cost: 0, unit: "per_video" },
3030
+ media: { format: "mp4", duration: options.duration }
3031
+ };
3032
+ }
3033
+ }
3034
+ throw new Error(`Google video generation timed out after 5 minutes`);
3035
+ }
3036
+ };
3037
+
2541
3038
  // src/noosphere.ts
2542
3039
  var Noosphere = class {
2543
3040
  config;
@@ -2780,6 +3277,12 @@ var Noosphere = class {
2780
3277
  if (hasAnyLLMKey) {
2781
3278
  this.registry.addProvider(new PiAiProvider(llmKeys));
2782
3279
  }
3280
+ if (keys.openai) {
3281
+ this.registry.addProvider(new OpenAIMediaProvider(keys.openai));
3282
+ }
3283
+ if (keys.google) {
3284
+ this.registry.addProvider(new GoogleMediaProvider(keys.google));
3285
+ }
2783
3286
  if (keys.fal) {
2784
3287
  this.registry.addProvider(new FalProvider(keys.fal));
2785
3288
  }
@@ -2959,11 +3462,13 @@ var Noosphere = class {
2959
3462
  // Annotate the CommonJS export names for ESM import in node:
2960
3463
  0 && (module.exports = {
2961
3464
  AudioCraftProvider,
3465
+ GoogleMediaProvider,
2962
3466
  HfLocalProvider,
2963
3467
  Noosphere,
2964
3468
  NoosphereError,
2965
3469
  OllamaProvider,
2966
3470
  OpenAICompatProvider,
3471
+ OpenAIMediaProvider,
2967
3472
  PROVIDER_IDS,
2968
3473
  PROVIDER_LOGOS,
2969
3474
  WhisperLocalProvider,