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 +28 -8
- package/dist/index.cjs +505 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.js +503 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
|
404
|
-
| `image` | hf-local, comfyui, fal, huggingface (cloud) |
|
|
405
|
-
| `video` | hf-local, comfyui, fal |
|
|
406
|
-
| `tts` |
|
|
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,
|