noosphere 0.8.0 → 0.9.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 +126 -8
- package/dist/index.cjs +712 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +88 -1
- package/dist/index.d.ts +88 -1
- package/dist/index.js +688 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -108,6 +108,136 @@ function resolveConfig(input) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// src/token-counter.ts
|
|
112
|
+
import { encoding_for_model } from "tiktoken";
|
|
113
|
+
var TIKTOKEN_MODEL_MAP = {
|
|
114
|
+
"gpt-4o": "gpt-4o",
|
|
115
|
+
"gpt-4o-mini": "gpt-4o-mini",
|
|
116
|
+
"gpt-4-turbo": "gpt-4-turbo",
|
|
117
|
+
"gpt-4": "gpt-4",
|
|
118
|
+
"gpt-3.5-turbo": "gpt-3.5-turbo"
|
|
119
|
+
};
|
|
120
|
+
function resolveTiktokenModel(model) {
|
|
121
|
+
if (model in TIKTOKEN_MODEL_MAP) return TIKTOKEN_MODEL_MAP[model];
|
|
122
|
+
for (const [prefix, tikModel] of Object.entries(TIKTOKEN_MODEL_MAP)) {
|
|
123
|
+
if (model.startsWith(prefix)) return tikModel;
|
|
124
|
+
}
|
|
125
|
+
return "gpt-4o";
|
|
126
|
+
}
|
|
127
|
+
function countWithTiktoken(messages, model) {
|
|
128
|
+
const tikModel = resolveTiktokenModel(model);
|
|
129
|
+
const enc = encoding_for_model(tikModel);
|
|
130
|
+
let tokens = 0;
|
|
131
|
+
for (const msg of messages) {
|
|
132
|
+
tokens += 4;
|
|
133
|
+
tokens += enc.encode(msg.role).length;
|
|
134
|
+
tokens += enc.encode(msg.content).length;
|
|
135
|
+
}
|
|
136
|
+
tokens += 2;
|
|
137
|
+
enc.free();
|
|
138
|
+
return tokens;
|
|
139
|
+
}
|
|
140
|
+
function countTokensOpenAI(messages, model = "gpt-4o") {
|
|
141
|
+
return countWithTiktoken(messages, model);
|
|
142
|
+
}
|
|
143
|
+
async function countTokensGoogle(messages, apiKey, model = "gemini-2.5-flash") {
|
|
144
|
+
const contents = messages.map((m) => ({
|
|
145
|
+
role: m.role === "assistant" ? "model" : "user",
|
|
146
|
+
parts: [{ text: m.content }]
|
|
147
|
+
}));
|
|
148
|
+
const res = await fetch(
|
|
149
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${model}:countTokens?key=${apiKey}`,
|
|
150
|
+
{
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: { "Content-Type": "application/json" },
|
|
153
|
+
body: JSON.stringify({ contents })
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
if (!res.ok) {
|
|
157
|
+
throw new Error(`Google countTokens failed (${res.status}): ${await res.text()}`);
|
|
158
|
+
}
|
|
159
|
+
const data = await res.json();
|
|
160
|
+
return data.totalTokens;
|
|
161
|
+
}
|
|
162
|
+
async function countTokensAnthropic(messages, apiKey, model = "claude-sonnet-4-20250514") {
|
|
163
|
+
const anthropicMessages = messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
|
|
164
|
+
const systemPrompt = messages.find((m) => m.role === "system")?.content;
|
|
165
|
+
const body = {
|
|
166
|
+
model,
|
|
167
|
+
messages: anthropicMessages
|
|
168
|
+
};
|
|
169
|
+
if (systemPrompt) body.system = systemPrompt;
|
|
170
|
+
const res = await fetch("https://api.anthropic.com/v1/messages/count_tokens", {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: {
|
|
173
|
+
"x-api-key": apiKey,
|
|
174
|
+
"anthropic-version": "2023-06-01",
|
|
175
|
+
"Content-Type": "application/json"
|
|
176
|
+
},
|
|
177
|
+
body: JSON.stringify(body)
|
|
178
|
+
});
|
|
179
|
+
if (!res.ok) {
|
|
180
|
+
throw new Error(`Anthropic countTokens failed (${res.status}): ${await res.text()}`);
|
|
181
|
+
}
|
|
182
|
+
const data = await res.json();
|
|
183
|
+
return data.input_tokens;
|
|
184
|
+
}
|
|
185
|
+
function countTokensGroq(messages, model = "llama-3.3-70b-versatile") {
|
|
186
|
+
return countWithTiktoken(messages, model);
|
|
187
|
+
}
|
|
188
|
+
function countTokensMistral(messages, model = "mistral-large-latest") {
|
|
189
|
+
return countWithTiktoken(messages, model);
|
|
190
|
+
}
|
|
191
|
+
function countTokensXai(messages, model = "grok-3") {
|
|
192
|
+
return countWithTiktoken(messages, model);
|
|
193
|
+
}
|
|
194
|
+
function countTokensCerebras(messages, model = "llama-3.3-70b") {
|
|
195
|
+
return countWithTiktoken(messages, model);
|
|
196
|
+
}
|
|
197
|
+
function countTokensOpenRouter(messages, model = "openai/gpt-4o") {
|
|
198
|
+
return countWithTiktoken(messages, model);
|
|
199
|
+
}
|
|
200
|
+
function countTokensOllama(messages, model = "llama3.2") {
|
|
201
|
+
return countWithTiktoken(messages, model);
|
|
202
|
+
}
|
|
203
|
+
var PROVIDER_MODEL_PREFIXES = [
|
|
204
|
+
{ prefixes: ["gemini", "imagen", "veo"], provider: "google" },
|
|
205
|
+
{ prefixes: ["claude"], provider: "anthropic" },
|
|
206
|
+
{ prefixes: ["gpt-", "o1", "o3", "o4", "chatgpt", "dall-e", "gpt-image", "tts-", "whisper", "sora"], provider: "openai" },
|
|
207
|
+
{ prefixes: ["grok"], provider: "xai" },
|
|
208
|
+
{ prefixes: ["mistral", "mixtral", "codestral", "ministral"], provider: "mistral" },
|
|
209
|
+
{ prefixes: ["llama", "gemma", "qwen", "deepseek", "phi"], provider: "groq" }
|
|
210
|
+
];
|
|
211
|
+
function inferProvider(model) {
|
|
212
|
+
const lower = model.toLowerCase();
|
|
213
|
+
for (const { prefixes, provider } of PROVIDER_MODEL_PREFIXES) {
|
|
214
|
+
for (const prefix of prefixes) {
|
|
215
|
+
if (lower.startsWith(prefix)) return provider;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return "unknown";
|
|
219
|
+
}
|
|
220
|
+
async function countTokens(options, apiKeys) {
|
|
221
|
+
const model = options.model ?? "gpt-4o";
|
|
222
|
+
const provider = options.provider ?? inferProvider(model);
|
|
223
|
+
if (provider === "google" && apiKeys?.google) {
|
|
224
|
+
const tokens2 = await countTokensGoogle(options.messages, apiKeys.google, model);
|
|
225
|
+
return { tokens: tokens2, model, provider: "google", method: "api" };
|
|
226
|
+
}
|
|
227
|
+
if (provider === "anthropic" && apiKeys?.anthropic) {
|
|
228
|
+
const tokens2 = await countTokensAnthropic(options.messages, apiKeys.anthropic, model);
|
|
229
|
+
return { tokens: tokens2, model, provider: "anthropic", method: "api" };
|
|
230
|
+
}
|
|
231
|
+
const tokens = countWithTiktoken(options.messages, model);
|
|
232
|
+
const resolvedProvider = provider === "unknown" ? "openai" : provider;
|
|
233
|
+
return {
|
|
234
|
+
tokens,
|
|
235
|
+
model,
|
|
236
|
+
provider: resolvedProvider,
|
|
237
|
+
method: provider === "openai" ? "tiktoken" : "tiktoken"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
111
241
|
// src/logos.ts
|
|
112
242
|
var CDN_BASE = "https://blockchainstarter.nyc3.digitaloceanspaces.com/noosphere/logos";
|
|
113
243
|
var PROVIDER_IDS = [
|
|
@@ -2508,6 +2638,522 @@ async function detectOpenAICompatServers() {
|
|
|
2508
2638
|
return providers;
|
|
2509
2639
|
}
|
|
2510
2640
|
|
|
2641
|
+
// src/providers/openai-media.ts
|
|
2642
|
+
var OPENAI_API_BASE = "https://api.openai.com/v1";
|
|
2643
|
+
var FETCH_TIMEOUT_MS5 = 8e3;
|
|
2644
|
+
var MODEL_PREFIX_MAP = [
|
|
2645
|
+
{ prefix: "dall-e-", modality: "image" },
|
|
2646
|
+
{ prefix: "gpt-image-", modality: "image" },
|
|
2647
|
+
{ prefix: "sora-", modality: "video" },
|
|
2648
|
+
{ prefix: "tts-", modality: "tts" },
|
|
2649
|
+
{ prefix: "whisper-", modality: "stt" }
|
|
2650
|
+
];
|
|
2651
|
+
function classifyModel(id) {
|
|
2652
|
+
for (const { prefix, modality } of MODEL_PREFIX_MAP) {
|
|
2653
|
+
if (id.startsWith(prefix)) return modality;
|
|
2654
|
+
}
|
|
2655
|
+
return null;
|
|
2656
|
+
}
|
|
2657
|
+
var OpenAIMediaProvider = class {
|
|
2658
|
+
constructor(apiKey) {
|
|
2659
|
+
this.apiKey = apiKey;
|
|
2660
|
+
}
|
|
2661
|
+
id = "openai-media";
|
|
2662
|
+
name = "OpenAI (Image, Video, TTS, STT)";
|
|
2663
|
+
modalities = ["image", "video", "tts", "stt"];
|
|
2664
|
+
isLocal = false;
|
|
2665
|
+
modelsCache = null;
|
|
2666
|
+
async ping() {
|
|
2667
|
+
try {
|
|
2668
|
+
const controller = new AbortController();
|
|
2669
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS5);
|
|
2670
|
+
try {
|
|
2671
|
+
const res = await fetch(`${OPENAI_API_BASE}/models`, {
|
|
2672
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
2673
|
+
signal: controller.signal
|
|
2674
|
+
});
|
|
2675
|
+
return res.ok;
|
|
2676
|
+
} finally {
|
|
2677
|
+
clearTimeout(timer);
|
|
2678
|
+
}
|
|
2679
|
+
} catch {
|
|
2680
|
+
return false;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
async listModels(modality) {
|
|
2684
|
+
if (this.modelsCache) {
|
|
2685
|
+
return modality ? this.modelsCache.filter((m) => m.modality === modality) : this.modelsCache;
|
|
2686
|
+
}
|
|
2687
|
+
try {
|
|
2688
|
+
const controller = new AbortController();
|
|
2689
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS5);
|
|
2690
|
+
let data;
|
|
2691
|
+
try {
|
|
2692
|
+
const res = await fetch(`${OPENAI_API_BASE}/models`, {
|
|
2693
|
+
headers: { Authorization: `Bearer ${this.apiKey}` },
|
|
2694
|
+
signal: controller.signal
|
|
2695
|
+
});
|
|
2696
|
+
if (!res.ok) return [];
|
|
2697
|
+
data = await res.json();
|
|
2698
|
+
} finally {
|
|
2699
|
+
clearTimeout(timer);
|
|
2700
|
+
}
|
|
2701
|
+
const entries = data?.data ?? [];
|
|
2702
|
+
const logo = getProviderLogo("openai");
|
|
2703
|
+
const models = [];
|
|
2704
|
+
for (const entry of entries) {
|
|
2705
|
+
const mod = classifyModel(entry.id);
|
|
2706
|
+
if (!mod) continue;
|
|
2707
|
+
const info = {
|
|
2708
|
+
id: entry.id,
|
|
2709
|
+
provider: "openai-media",
|
|
2710
|
+
name: entry.id,
|
|
2711
|
+
modality: mod,
|
|
2712
|
+
local: false,
|
|
2713
|
+
cost: { price: 0, unit: "per_request" },
|
|
2714
|
+
logo,
|
|
2715
|
+
description: entry.description,
|
|
2716
|
+
capabilities: this.getCapabilities(entry.id, mod)
|
|
2717
|
+
};
|
|
2718
|
+
models.push(info);
|
|
2719
|
+
}
|
|
2720
|
+
this.modelsCache = models;
|
|
2721
|
+
return modality ? models.filter((m) => m.modality === modality) : models;
|
|
2722
|
+
} catch {
|
|
2723
|
+
return [];
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
async image(options) {
|
|
2727
|
+
const model = options.model ?? "gpt-image-1";
|
|
2728
|
+
const width = options.width ?? 1024;
|
|
2729
|
+
const height = options.height ?? 1024;
|
|
2730
|
+
const start = Date.now();
|
|
2731
|
+
const isGptImage = model.startsWith("gpt-image-");
|
|
2732
|
+
const body = {
|
|
2733
|
+
model,
|
|
2734
|
+
prompt: options.prompt,
|
|
2735
|
+
n: 1,
|
|
2736
|
+
size: `${width}x${height}`
|
|
2737
|
+
};
|
|
2738
|
+
if (!isGptImage) {
|
|
2739
|
+
body.response_format = "url";
|
|
2740
|
+
}
|
|
2741
|
+
const res = await fetch(`${OPENAI_API_BASE}/images/generations`, {
|
|
2742
|
+
method: "POST",
|
|
2743
|
+
headers: {
|
|
2744
|
+
"Content-Type": "application/json",
|
|
2745
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2746
|
+
},
|
|
2747
|
+
body: JSON.stringify(body)
|
|
2748
|
+
});
|
|
2749
|
+
if (!res.ok) {
|
|
2750
|
+
const errorBody = await res.text();
|
|
2751
|
+
throw new Error(`OpenAI image generation failed (${res.status}): ${errorBody}`);
|
|
2752
|
+
}
|
|
2753
|
+
const data = await res.json();
|
|
2754
|
+
const item = data?.data?.[0];
|
|
2755
|
+
const result = {
|
|
2756
|
+
provider: "openai-media",
|
|
2757
|
+
model,
|
|
2758
|
+
modality: "image",
|
|
2759
|
+
latencyMs: Date.now() - start,
|
|
2760
|
+
usage: {
|
|
2761
|
+
cost: 0,
|
|
2762
|
+
unit: "per_image"
|
|
2763
|
+
},
|
|
2764
|
+
media: {
|
|
2765
|
+
width,
|
|
2766
|
+
height,
|
|
2767
|
+
format: "png"
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
if (item?.b64_json) {
|
|
2771
|
+
result.buffer = Buffer.from(item.b64_json, "base64");
|
|
2772
|
+
} else if (item?.url) {
|
|
2773
|
+
result.url = item.url;
|
|
2774
|
+
}
|
|
2775
|
+
return result;
|
|
2776
|
+
}
|
|
2777
|
+
async speak(options) {
|
|
2778
|
+
const model = options.model ?? "tts-1";
|
|
2779
|
+
const voice = options.voice ?? "alloy";
|
|
2780
|
+
const format = options.format ?? "mp3";
|
|
2781
|
+
const speed = options.speed ?? 1;
|
|
2782
|
+
const start = Date.now();
|
|
2783
|
+
const body = {
|
|
2784
|
+
model,
|
|
2785
|
+
input: options.text,
|
|
2786
|
+
voice,
|
|
2787
|
+
response_format: format,
|
|
2788
|
+
speed
|
|
2789
|
+
};
|
|
2790
|
+
const res = await fetch(`${OPENAI_API_BASE}/audio/speech`, {
|
|
2791
|
+
method: "POST",
|
|
2792
|
+
headers: {
|
|
2793
|
+
"Content-Type": "application/json",
|
|
2794
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2795
|
+
},
|
|
2796
|
+
body: JSON.stringify(body)
|
|
2797
|
+
});
|
|
2798
|
+
if (!res.ok) {
|
|
2799
|
+
const errorBody = await res.text();
|
|
2800
|
+
throw new Error(`OpenAI TTS failed (${res.status}): ${errorBody}`);
|
|
2801
|
+
}
|
|
2802
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
2803
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
2804
|
+
return {
|
|
2805
|
+
buffer,
|
|
2806
|
+
provider: "openai-media",
|
|
2807
|
+
model,
|
|
2808
|
+
modality: "tts",
|
|
2809
|
+
latencyMs: Date.now() - start,
|
|
2810
|
+
usage: {
|
|
2811
|
+
cost: 0,
|
|
2812
|
+
input: options.text.length,
|
|
2813
|
+
unit: "per_1k_chars"
|
|
2814
|
+
},
|
|
2815
|
+
media: {
|
|
2816
|
+
format
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
async video(options) {
|
|
2821
|
+
const model = options.model ?? "sora-2";
|
|
2822
|
+
const start = Date.now();
|
|
2823
|
+
const body = {
|
|
2824
|
+
model,
|
|
2825
|
+
prompt: options.prompt,
|
|
2826
|
+
n: 1
|
|
2827
|
+
};
|
|
2828
|
+
if (options.duration) body.duration = options.duration;
|
|
2829
|
+
if (options.width && options.height) body.size = `${options.width}x${options.height}`;
|
|
2830
|
+
const res = await fetch(`${OPENAI_API_BASE}/videos/generations`, {
|
|
2831
|
+
method: "POST",
|
|
2832
|
+
headers: {
|
|
2833
|
+
"Content-Type": "application/json",
|
|
2834
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
2835
|
+
},
|
|
2836
|
+
body: JSON.stringify(body)
|
|
2837
|
+
});
|
|
2838
|
+
if (!res.ok) {
|
|
2839
|
+
const errorBody = await res.text();
|
|
2840
|
+
throw new Error(`OpenAI video generation failed (${res.status}): ${errorBody}`);
|
|
2841
|
+
}
|
|
2842
|
+
const data = await res.json();
|
|
2843
|
+
const videoUrl = data?.data?.[0]?.url;
|
|
2844
|
+
return {
|
|
2845
|
+
url: videoUrl,
|
|
2846
|
+
provider: "openai-media",
|
|
2847
|
+
model,
|
|
2848
|
+
modality: "video",
|
|
2849
|
+
latencyMs: Date.now() - start,
|
|
2850
|
+
usage: {
|
|
2851
|
+
cost: 0,
|
|
2852
|
+
unit: "per_video"
|
|
2853
|
+
},
|
|
2854
|
+
media: {
|
|
2855
|
+
duration: options.duration,
|
|
2856
|
+
width: options.width,
|
|
2857
|
+
height: options.height
|
|
2858
|
+
}
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
getCapabilities(id, modality) {
|
|
2862
|
+
if (modality === "image") {
|
|
2863
|
+
return {
|
|
2864
|
+
maxWidth: id.startsWith("dall-e-3") ? 1792 : 1024,
|
|
2865
|
+
maxHeight: id.startsWith("dall-e-3") ? 1792 : 1024
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
if (modality === "tts") {
|
|
2869
|
+
return {
|
|
2870
|
+
voices: ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"]
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
if (modality === "video") {
|
|
2874
|
+
return {
|
|
2875
|
+
maxDuration: id.includes("pro") ? 20 : 10,
|
|
2876
|
+
supportsStreaming: false
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
if (modality === "stt") {
|
|
2880
|
+
return {
|
|
2881
|
+
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"]
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
return void 0;
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
|
|
2888
|
+
// src/providers/google-media.ts
|
|
2889
|
+
var GOOGLE_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
2890
|
+
var FETCH_TIMEOUT_MS6 = 8e3;
|
|
2891
|
+
var GOOGLE_TTS_VOICES = [
|
|
2892
|
+
"Aoede",
|
|
2893
|
+
"Charon",
|
|
2894
|
+
"Fenrir",
|
|
2895
|
+
"Kore",
|
|
2896
|
+
"Puck",
|
|
2897
|
+
"Leda",
|
|
2898
|
+
"Orus",
|
|
2899
|
+
"Perseus",
|
|
2900
|
+
"Zephyr",
|
|
2901
|
+
"Callirrhoe"
|
|
2902
|
+
];
|
|
2903
|
+
function classifyGoogleModel(model) {
|
|
2904
|
+
const name = (model.name ?? "").replace("models/", "");
|
|
2905
|
+
const methods = model.supportedGenerationMethods ?? [];
|
|
2906
|
+
if (name.startsWith("imagen") && methods.includes("predict")) return "image";
|
|
2907
|
+
if (name.startsWith("veo") && methods.includes("predictLongRunning")) return "video";
|
|
2908
|
+
if (name.includes("-tts") && methods.includes("generateContent")) return "tts";
|
|
2909
|
+
return null;
|
|
2910
|
+
}
|
|
2911
|
+
var GoogleMediaProvider = class {
|
|
2912
|
+
constructor(apiKey) {
|
|
2913
|
+
this.apiKey = apiKey;
|
|
2914
|
+
}
|
|
2915
|
+
id = "google-media";
|
|
2916
|
+
name = "Google (Image, Video, TTS)";
|
|
2917
|
+
modalities = ["image", "video", "tts"];
|
|
2918
|
+
isLocal = false;
|
|
2919
|
+
modelsCache = null;
|
|
2920
|
+
async ping() {
|
|
2921
|
+
try {
|
|
2922
|
+
const controller = new AbortController();
|
|
2923
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS6);
|
|
2924
|
+
try {
|
|
2925
|
+
const res = await fetch(`${GOOGLE_API_BASE}/models?key=${this.apiKey}`, {
|
|
2926
|
+
signal: controller.signal
|
|
2927
|
+
});
|
|
2928
|
+
return res.ok;
|
|
2929
|
+
} finally {
|
|
2930
|
+
clearTimeout(timer);
|
|
2931
|
+
}
|
|
2932
|
+
} catch {
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
async listModels(modality) {
|
|
2937
|
+
if (this.modelsCache) {
|
|
2938
|
+
return modality ? this.modelsCache.filter((m) => m.modality === modality) : this.modelsCache;
|
|
2939
|
+
}
|
|
2940
|
+
try {
|
|
2941
|
+
const controller = new AbortController();
|
|
2942
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS6);
|
|
2943
|
+
let data;
|
|
2944
|
+
try {
|
|
2945
|
+
const res = await fetch(`${GOOGLE_API_BASE}/models?key=${this.apiKey}`, {
|
|
2946
|
+
signal: controller.signal
|
|
2947
|
+
});
|
|
2948
|
+
if (!res.ok) return [];
|
|
2949
|
+
data = await res.json();
|
|
2950
|
+
} finally {
|
|
2951
|
+
clearTimeout(timer);
|
|
2952
|
+
}
|
|
2953
|
+
const entries = data?.models ?? [];
|
|
2954
|
+
const logo = getProviderLogo("google");
|
|
2955
|
+
const models = [];
|
|
2956
|
+
for (const entry of entries) {
|
|
2957
|
+
const modality2 = classifyGoogleModel(entry);
|
|
2958
|
+
if (!modality2) continue;
|
|
2959
|
+
const fullName = entry.name ?? "";
|
|
2960
|
+
const modelId = fullName.startsWith("models/") ? fullName.slice("models/".length) : fullName;
|
|
2961
|
+
const info = {
|
|
2962
|
+
id: modelId,
|
|
2963
|
+
provider: "google-media",
|
|
2964
|
+
name: entry.displayName ?? modelId,
|
|
2965
|
+
modality: modality2,
|
|
2966
|
+
local: false,
|
|
2967
|
+
cost: { price: 0, unit: modality2 === "video" ? "per_video" : "per_image" },
|
|
2968
|
+
logo,
|
|
2969
|
+
description: entry.description,
|
|
2970
|
+
capabilities: modality2 === "video" ? { maxDuration: 8, supportsStreaming: false } : modality2 === "tts" ? { voices: GOOGLE_TTS_VOICES } : void 0
|
|
2971
|
+
};
|
|
2972
|
+
models.push(info);
|
|
2973
|
+
}
|
|
2974
|
+
this.modelsCache = models;
|
|
2975
|
+
return modality ? models.filter((m) => m.modality === modality) : models;
|
|
2976
|
+
} catch {
|
|
2977
|
+
return [];
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
async image(options) {
|
|
2981
|
+
const model = options.model ?? "imagen-4.0-generate-001";
|
|
2982
|
+
const start = Date.now();
|
|
2983
|
+
const body = {
|
|
2984
|
+
instances: [{ prompt: options.prompt }],
|
|
2985
|
+
parameters: {
|
|
2986
|
+
sampleCount: 1
|
|
2987
|
+
}
|
|
2988
|
+
};
|
|
2989
|
+
const res = await fetch(
|
|
2990
|
+
`${GOOGLE_API_BASE}/models/${model}:predict?key=${this.apiKey}`,
|
|
2991
|
+
{
|
|
2992
|
+
method: "POST",
|
|
2993
|
+
headers: { "Content-Type": "application/json" },
|
|
2994
|
+
body: JSON.stringify(body)
|
|
2995
|
+
}
|
|
2996
|
+
);
|
|
2997
|
+
if (!res.ok) {
|
|
2998
|
+
const errorBody = await res.text();
|
|
2999
|
+
throw new Error(`Google image generation failed (${res.status}): ${errorBody}`);
|
|
3000
|
+
}
|
|
3001
|
+
const data = await res.json();
|
|
3002
|
+
const base64 = data?.predictions?.[0]?.bytesBase64Encoded ?? data?.generatedImages?.[0]?.image?.imageBytes;
|
|
3003
|
+
if (!base64) {
|
|
3004
|
+
throw new Error("Google image generation returned no image data");
|
|
3005
|
+
}
|
|
3006
|
+
const buffer = Buffer.from(base64, "base64");
|
|
3007
|
+
return {
|
|
3008
|
+
buffer,
|
|
3009
|
+
provider: "google-media",
|
|
3010
|
+
model,
|
|
3011
|
+
modality: "image",
|
|
3012
|
+
latencyMs: Date.now() - start,
|
|
3013
|
+
usage: {
|
|
3014
|
+
cost: 0,
|
|
3015
|
+
unit: "per_image"
|
|
3016
|
+
},
|
|
3017
|
+
media: {
|
|
3018
|
+
format: "png"
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
async speak(options) {
|
|
3023
|
+
const model = options.model ?? "gemini-2.5-flash-preview-tts";
|
|
3024
|
+
const voice = options.voice ?? "Kore";
|
|
3025
|
+
const start = Date.now();
|
|
3026
|
+
const body = {
|
|
3027
|
+
contents: [{ parts: [{ text: options.text }] }],
|
|
3028
|
+
generationConfig: {
|
|
3029
|
+
response_modalities: ["AUDIO"],
|
|
3030
|
+
speech_config: {
|
|
3031
|
+
voiceConfig: {
|
|
3032
|
+
prebuiltVoiceConfig: { voiceName: voice }
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3037
|
+
const res = await fetch(
|
|
3038
|
+
`${GOOGLE_API_BASE}/models/${model}:generateContent?key=${this.apiKey}`,
|
|
3039
|
+
{
|
|
3040
|
+
method: "POST",
|
|
3041
|
+
headers: { "Content-Type": "application/json" },
|
|
3042
|
+
body: JSON.stringify(body)
|
|
3043
|
+
}
|
|
3044
|
+
);
|
|
3045
|
+
if (!res.ok) {
|
|
3046
|
+
const errorBody = await res.text();
|
|
3047
|
+
throw new Error(`Google TTS failed (${res.status}): ${errorBody}`);
|
|
3048
|
+
}
|
|
3049
|
+
const data = await res.json();
|
|
3050
|
+
const inlineData = data?.candidates?.[0]?.content?.parts?.[0]?.inlineData;
|
|
3051
|
+
if (!inlineData?.data) {
|
|
3052
|
+
throw new Error("Google TTS returned no audio data");
|
|
3053
|
+
}
|
|
3054
|
+
const buffer = Buffer.from(inlineData.data, "base64");
|
|
3055
|
+
return {
|
|
3056
|
+
buffer,
|
|
3057
|
+
provider: "google-media",
|
|
3058
|
+
model,
|
|
3059
|
+
modality: "tts",
|
|
3060
|
+
latencyMs: Date.now() - start,
|
|
3061
|
+
usage: {
|
|
3062
|
+
cost: 0,
|
|
3063
|
+
input: options.text.length,
|
|
3064
|
+
unit: "per_1k_chars"
|
|
3065
|
+
},
|
|
3066
|
+
media: {
|
|
3067
|
+
format: "wav"
|
|
3068
|
+
// Google returns PCM L16, essentially WAV
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
async video(options) {
|
|
3073
|
+
const model = options.model ?? "veo-2.0-generate-001";
|
|
3074
|
+
const start = Date.now();
|
|
3075
|
+
const body = {
|
|
3076
|
+
instances: [{ prompt: options.prompt }],
|
|
3077
|
+
parameters: {
|
|
3078
|
+
sampleCount: 1
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
if (options.duration) body.parameters.durationSeconds = options.duration;
|
|
3082
|
+
const res = await fetch(
|
|
3083
|
+
`${GOOGLE_API_BASE}/models/${model}:predictLongRunning?key=${this.apiKey}`,
|
|
3084
|
+
{
|
|
3085
|
+
method: "POST",
|
|
3086
|
+
headers: { "Content-Type": "application/json" },
|
|
3087
|
+
body: JSON.stringify(body)
|
|
3088
|
+
}
|
|
3089
|
+
);
|
|
3090
|
+
if (!res.ok) {
|
|
3091
|
+
const errorBody = await res.text();
|
|
3092
|
+
throw new Error(`Google video generation failed (${res.status}): ${errorBody}`);
|
|
3093
|
+
}
|
|
3094
|
+
const operation = await res.json();
|
|
3095
|
+
const operationName = operation?.name;
|
|
3096
|
+
if (!operationName) {
|
|
3097
|
+
throw new Error("Google video generation returned no operation name");
|
|
3098
|
+
}
|
|
3099
|
+
const deadline = Date.now() + 3e5;
|
|
3100
|
+
while (Date.now() < deadline) {
|
|
3101
|
+
await new Promise((r) => setTimeout(r, 5e3));
|
|
3102
|
+
const pollRes = await fetch(
|
|
3103
|
+
`${GOOGLE_API_BASE}/${operationName}?key=${this.apiKey}`
|
|
3104
|
+
);
|
|
3105
|
+
if (!pollRes.ok) continue;
|
|
3106
|
+
const status = await pollRes.json();
|
|
3107
|
+
if (status.done) {
|
|
3108
|
+
if (status.error) {
|
|
3109
|
+
throw new Error(`Google video generation error: ${status.error.message ?? JSON.stringify(status.error)}`);
|
|
3110
|
+
}
|
|
3111
|
+
const resp = status.response ?? {};
|
|
3112
|
+
const samples = resp.generateVideoResponse?.generatedSamples ?? resp.generatedSamples ?? [];
|
|
3113
|
+
const video = samples[0]?.video;
|
|
3114
|
+
if (video?.bytesBase64Encoded) {
|
|
3115
|
+
return {
|
|
3116
|
+
buffer: Buffer.from(video.bytesBase64Encoded, "base64"),
|
|
3117
|
+
provider: "google-media",
|
|
3118
|
+
model,
|
|
3119
|
+
modality: "video",
|
|
3120
|
+
latencyMs: Date.now() - start,
|
|
3121
|
+
usage: { cost: 0, unit: "per_video" },
|
|
3122
|
+
media: { format: "mp4", duration: options.duration }
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
if (video?.uri) {
|
|
3126
|
+
const separator = video.uri.includes("?") ? "&" : "?";
|
|
3127
|
+
const videoRes = await fetch(`${video.uri}${separator}key=${this.apiKey}`, { redirect: "follow" });
|
|
3128
|
+
if (videoRes.ok) {
|
|
3129
|
+
const buffer = Buffer.from(await videoRes.arrayBuffer());
|
|
3130
|
+
return {
|
|
3131
|
+
buffer,
|
|
3132
|
+
provider: "google-media",
|
|
3133
|
+
model,
|
|
3134
|
+
modality: "video",
|
|
3135
|
+
latencyMs: Date.now() - start,
|
|
3136
|
+
usage: { cost: 0, unit: "per_video" },
|
|
3137
|
+
media: { format: "mp4", duration: options.duration }
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
return {
|
|
3141
|
+
url: video.uri,
|
|
3142
|
+
provider: "google-media",
|
|
3143
|
+
model,
|
|
3144
|
+
modality: "video",
|
|
3145
|
+
latencyMs: Date.now() - start,
|
|
3146
|
+
usage: { cost: 0, unit: "per_video" },
|
|
3147
|
+
media: { format: "mp4", duration: options.duration }
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
throw new Error("Google video generation completed but returned no video data");
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
throw new Error(`Google video generation timed out after 5 minutes`);
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
|
|
2511
3157
|
// src/noosphere.ts
|
|
2512
3158
|
var Noosphere = class {
|
|
2513
3159
|
config;
|
|
@@ -2698,6 +3344,12 @@ var Noosphere = class {
|
|
|
2698
3344
|
getUsage(options) {
|
|
2699
3345
|
return this.tracker.getSummary(options);
|
|
2700
3346
|
}
|
|
3347
|
+
async countTokens(options) {
|
|
3348
|
+
const keys = {};
|
|
3349
|
+
if (this.config.keys?.google) keys.google = this.config.keys.google;
|
|
3350
|
+
if (this.config.keys?.anthropic) keys.anthropic = this.config.keys.anthropic;
|
|
3351
|
+
return countTokens(options, keys);
|
|
3352
|
+
}
|
|
2701
3353
|
// --- Local Model Management ---
|
|
2702
3354
|
async installModel(name) {
|
|
2703
3355
|
if (!this.initialized) await this.init();
|
|
@@ -2750,6 +3402,12 @@ var Noosphere = class {
|
|
|
2750
3402
|
if (hasAnyLLMKey) {
|
|
2751
3403
|
this.registry.addProvider(new PiAiProvider(llmKeys));
|
|
2752
3404
|
}
|
|
3405
|
+
if (keys.openai) {
|
|
3406
|
+
this.registry.addProvider(new OpenAIMediaProvider(keys.openai));
|
|
3407
|
+
}
|
|
3408
|
+
if (keys.google) {
|
|
3409
|
+
this.registry.addProvider(new GoogleMediaProvider(keys.google));
|
|
3410
|
+
}
|
|
2753
3411
|
if (keys.fal) {
|
|
2754
3412
|
this.registry.addProvider(new FalProvider(keys.fal));
|
|
2755
3413
|
}
|
|
@@ -2926,18 +3584,47 @@ var Noosphere = class {
|
|
|
2926
3584
|
await this.tracker.record(event);
|
|
2927
3585
|
}
|
|
2928
3586
|
};
|
|
3587
|
+
|
|
3588
|
+
// src/index.ts
|
|
3589
|
+
import { agentLoop } from "@mariozechner/pi-ai";
|
|
3590
|
+
import { calculateCost, getModel, getModels as getModels2, getProviders as getProviders2 } from "@mariozechner/pi-ai";
|
|
3591
|
+
import { stream as stream2, complete as complete2, streamSimple, completeSimple } from "@mariozechner/pi-ai";
|
|
3592
|
+
import { setApiKey as setApiKey2, getApiKey } from "@mariozechner/pi-ai";
|
|
2929
3593
|
export {
|
|
2930
3594
|
AudioCraftProvider,
|
|
3595
|
+
GoogleMediaProvider,
|
|
2931
3596
|
HfLocalProvider,
|
|
2932
3597
|
Noosphere,
|
|
2933
3598
|
NoosphereError,
|
|
2934
3599
|
OllamaProvider,
|
|
2935
3600
|
OpenAICompatProvider,
|
|
3601
|
+
OpenAIMediaProvider,
|
|
2936
3602
|
PROVIDER_IDS,
|
|
2937
3603
|
PROVIDER_LOGOS,
|
|
2938
3604
|
WhisperLocalProvider,
|
|
3605
|
+
agentLoop,
|
|
3606
|
+
calculateCost,
|
|
3607
|
+
countTokens,
|
|
3608
|
+
countTokensAnthropic,
|
|
3609
|
+
countTokensCerebras,
|
|
3610
|
+
countTokensGoogle,
|
|
3611
|
+
countTokensGroq,
|
|
3612
|
+
countTokensMistral,
|
|
3613
|
+
countTokensOllama,
|
|
3614
|
+
countTokensOpenAI,
|
|
3615
|
+
countTokensOpenRouter,
|
|
3616
|
+
countTokensXai,
|
|
2939
3617
|
detectOpenAICompatServers,
|
|
2940
3618
|
getAllProviderLogos,
|
|
2941
|
-
|
|
3619
|
+
getApiKey,
|
|
3620
|
+
getModel as getPiModel,
|
|
3621
|
+
getModels2 as getPiModels,
|
|
3622
|
+
getProviders2 as getPiProviders,
|
|
3623
|
+
getProviderLogo,
|
|
3624
|
+
complete2 as piComplete,
|
|
3625
|
+
completeSimple as piCompleteSimple,
|
|
3626
|
+
stream2 as piStream,
|
|
3627
|
+
streamSimple as piStreamSimple,
|
|
3628
|
+
setApiKey2 as setApiKey
|
|
2942
3629
|
};
|
|
2943
3630
|
//# sourceMappingURL=index.js.map
|