noosphere 0.9.0 → 0.9.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
@@ -16,6 +16,8 @@ One import. Every model. Every modality.
16
16
  - **30+ HuggingFace tasks** — LLM, image, TTS, translation, summarization, classification, and more
17
17
  - **Local-first architecture** — Auto-detects Ollama, ComfyUI, Whisper, AudioCraft, Piper, and Kokoro on your machine
18
18
  - **Org-aware logos** — HuggingFace models show the real org logo (Meta, Google, NVIDIA) instead of generic HF logo
19
+ - **Pre-request token counting** — Count tokens before sending, for ALL providers (OpenAI/Groq/Ollama via tiktoken, Google/Anthropic via API)
20
+ - **Full pi-ai access** — Agent loop with tool calling, preprocessor (compaction hook), `calculateCost`, direct stream/complete APIs — all re-exported
19
21
  - **Agentic capabilities** — Tool use, function calling, reasoning/thinking, vision, and agent loops via Pi-AI
20
22
  - **Failover & retry** — Automatic retries with exponential backoff and cross-provider failover
21
23
  - **Usage tracking** — Real-time cost, latency, and token tracking across all providers
@@ -629,6 +631,102 @@ Noosphere auto-detects all local runtimes on startup:
629
631
 
630
632
  ---
631
633
 
634
+ ## Pre-Request Token Counting
635
+
636
+ Count tokens **before** sending a request to any provider. Know the cost upfront.
637
+
638
+ ```typescript
639
+ // Via Noosphere instance (auto-routes by model)
640
+ const result = await ai.countTokens({
641
+ messages: [
642
+ { role: 'system', content: 'You are a helpful assistant.' },
643
+ { role: 'user', content: 'Explain quantum computing.' },
644
+ ],
645
+ model: 'gpt-4o',
646
+ });
647
+ console.log(result.tokens); // 26
648
+ console.log(result.method); // "tiktoken" (instant, local)
649
+ console.log(result.provider); // "openai"
650
+
651
+ // Google — exact count via API
652
+ const google = await ai.countTokens({
653
+ messages: [{ role: 'user', content: 'Hello!' }],
654
+ model: 'gemini-2.5-flash',
655
+ });
656
+ console.log(google.tokens); // 3
657
+ console.log(google.method); // "api" (exact)
658
+ ```
659
+
660
+ **Token counting by provider:**
661
+
662
+ | Provider | Method | Speed | Accuracy |
663
+ |---|---|---|---|
664
+ | **OpenAI** (GPT-4o, o1, o3, o4, GPT-5) | tiktoken (local) | Instant | Exact |
665
+ | **Google** (Gemini) | `/countTokens` API | ~200ms | Exact |
666
+ | **Anthropic** (Claude) | `/messages/count_tokens` API | ~200ms | Exact |
667
+ | **Groq** (Llama, Mixtral, Gemma) | tiktoken (local) | Instant | Exact |
668
+ | **Cerebras** (Llama) | tiktoken (local) | Instant | Exact |
669
+ | **Mistral** (Mistral, Mixtral, Codestral) | tiktoken (local) | Instant | Close approx |
670
+ | **xAI** (Grok) | tiktoken (local) | Instant | Close approx |
671
+ | **OpenRouter** (all models) | tiktoken (local) | Instant | Close approx |
672
+ | **Ollama** (all local models) | tiktoken (local) | Instant | Close approx |
673
+
674
+ You can also use standalone functions without a Noosphere instance:
675
+
676
+ ```typescript
677
+ import {
678
+ countTokensOpenAI, countTokensGoogle, countTokensAnthropic,
679
+ countTokensGroq, countTokensMistral, countTokensXai,
680
+ countTokensCerebras, countTokensOpenRouter, countTokensOllama,
681
+ } from 'noosphere';
682
+
683
+ // Local (instant, no API key needed)
684
+ const tokens = countTokensOpenAI(messages, 'gpt-4o'); // 26
685
+ const groq = countTokensGroq(messages, 'llama-3.3-70b'); // 26
686
+ const ollama = countTokensOllama(messages, 'qwen3:8b'); // 26
687
+
688
+ // API-based (exact, needs key)
689
+ const google = await countTokensGoogle(messages, GEMINI_KEY, 'gemini-2.5-flash'); // 16
690
+ const claude = await countTokensAnthropic(messages, ANTHROPIC_KEY, 'claude-sonnet-4-20250514'); // exact
691
+ ```
692
+
693
+ ---
694
+
695
+ ## Agent Loop & pi-ai Access
696
+
697
+ Noosphere re-exports the full [pi-ai](https://github.com/nicholasgriffintn/pi-ai) library for direct access to agent loops, tool calling, cost calculation, and streaming APIs.
698
+
699
+ ```typescript
700
+ import {
701
+ agentLoop, calculateCost,
702
+ piStream, piComplete, piStreamSimple, piCompleteSimple,
703
+ setApiKey, getApiKey, getPiModel, getPiModels, getPiProviders,
704
+ } from 'noosphere';
705
+
706
+ // Agent loop with tool calling and preprocessor (compaction hook)
707
+ import type { AgentLoopConfig, AgentContext, AgentTool } from 'noosphere';
708
+
709
+ const config: AgentLoopConfig = {
710
+ model: getPiModel('openai', 'gpt-4o'),
711
+ // Preprocessor runs before each LLM call — use for context compaction
712
+ preprocessor: async (messages) => {
713
+ // Truncate old messages, summarize, etc.
714
+ if (messages.length > 50) {
715
+ return messages.slice(-20); // keep last 20
716
+ }
717
+ return messages;
718
+ },
719
+ };
720
+
721
+ // Calculate cost before sending
722
+ const model = getPiModel('openai', 'gpt-4o');
723
+ const usage = { input: 1000, output: 500, cacheRead: 0, cacheWrite: 0 };
724
+ const cost = calculateCost(model, usage);
725
+ console.log(cost.total); // $0.00625
726
+ ```
727
+
728
+ ---
729
+
632
730
  ## Configuration
633
731
 
634
732
  API keys are resolved from the constructor config or environment variables (config takes priority):
package/dist/index.cjs CHANGED
@@ -31,9 +31,30 @@ __export(index_exports, {
31
31
  PROVIDER_IDS: () => PROVIDER_IDS,
32
32
  PROVIDER_LOGOS: () => PROVIDER_LOGOS,
33
33
  WhisperLocalProvider: () => WhisperLocalProvider,
34
+ agentLoop: () => import_pi_ai3.agentLoop,
35
+ calculateCost: () => import_pi_ai4.calculateCost,
36
+ countTokens: () => countTokens,
37
+ countTokensAnthropic: () => countTokensAnthropic,
38
+ countTokensCerebras: () => countTokensCerebras,
39
+ countTokensGoogle: () => countTokensGoogle,
40
+ countTokensGroq: () => countTokensGroq,
41
+ countTokensMistral: () => countTokensMistral,
42
+ countTokensOllama: () => countTokensOllama,
43
+ countTokensOpenAI: () => countTokensOpenAI,
44
+ countTokensOpenRouter: () => countTokensOpenRouter,
45
+ countTokensXai: () => countTokensXai,
34
46
  detectOpenAICompatServers: () => detectOpenAICompatServers,
35
47
  getAllProviderLogos: () => getAllProviderLogos,
36
- getProviderLogo: () => getProviderLogo
48
+ getApiKey: () => import_pi_ai6.getApiKey,
49
+ getPiModel: () => import_pi_ai4.getModel,
50
+ getPiModels: () => import_pi_ai4.getModels,
51
+ getPiProviders: () => import_pi_ai4.getProviders,
52
+ getProviderLogo: () => getProviderLogo,
53
+ piComplete: () => import_pi_ai5.complete,
54
+ piCompleteSimple: () => import_pi_ai5.completeSimple,
55
+ piStream: () => import_pi_ai5.stream,
56
+ piStreamSimple: () => import_pi_ai5.streamSimple,
57
+ setApiKey: () => import_pi_ai6.setApiKey
37
58
  });
38
59
  module.exports = __toCommonJS(index_exports);
39
60
 
@@ -140,6 +161,136 @@ function resolveConfig(input) {
140
161
  };
141
162
  }
142
163
 
164
+ // src/token-counter.ts
165
+ var import_tiktoken = require("tiktoken");
166
+ var TIKTOKEN_MODEL_MAP = {
167
+ "gpt-4o": "gpt-4o",
168
+ "gpt-4o-mini": "gpt-4o-mini",
169
+ "gpt-4-turbo": "gpt-4-turbo",
170
+ "gpt-4": "gpt-4",
171
+ "gpt-3.5-turbo": "gpt-3.5-turbo"
172
+ };
173
+ function resolveTiktokenModel(model) {
174
+ if (model in TIKTOKEN_MODEL_MAP) return TIKTOKEN_MODEL_MAP[model];
175
+ for (const [prefix, tikModel] of Object.entries(TIKTOKEN_MODEL_MAP)) {
176
+ if (model.startsWith(prefix)) return tikModel;
177
+ }
178
+ return "gpt-4o";
179
+ }
180
+ function countWithTiktoken(messages, model) {
181
+ const tikModel = resolveTiktokenModel(model);
182
+ const enc = (0, import_tiktoken.encoding_for_model)(tikModel);
183
+ let tokens = 0;
184
+ for (const msg of messages) {
185
+ tokens += 4;
186
+ tokens += enc.encode(msg.role).length;
187
+ tokens += enc.encode(msg.content).length;
188
+ }
189
+ tokens += 2;
190
+ enc.free();
191
+ return tokens;
192
+ }
193
+ function countTokensOpenAI(messages, model = "gpt-4o") {
194
+ return countWithTiktoken(messages, model);
195
+ }
196
+ async function countTokensGoogle(messages, apiKey, model = "gemini-2.5-flash") {
197
+ const contents = messages.map((m) => ({
198
+ role: m.role === "assistant" ? "model" : "user",
199
+ parts: [{ text: m.content }]
200
+ }));
201
+ const res = await fetch(
202
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:countTokens?key=${apiKey}`,
203
+ {
204
+ method: "POST",
205
+ headers: { "Content-Type": "application/json" },
206
+ body: JSON.stringify({ contents })
207
+ }
208
+ );
209
+ if (!res.ok) {
210
+ throw new Error(`Google countTokens failed (${res.status}): ${await res.text()}`);
211
+ }
212
+ const data = await res.json();
213
+ return data.totalTokens;
214
+ }
215
+ async function countTokensAnthropic(messages, apiKey, model = "claude-sonnet-4-20250514") {
216
+ const anthropicMessages = messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, content: m.content }));
217
+ const systemPrompt = messages.find((m) => m.role === "system")?.content;
218
+ const body = {
219
+ model,
220
+ messages: anthropicMessages
221
+ };
222
+ if (systemPrompt) body.system = systemPrompt;
223
+ const res = await fetch("https://api.anthropic.com/v1/messages/count_tokens", {
224
+ method: "POST",
225
+ headers: {
226
+ "x-api-key": apiKey,
227
+ "anthropic-version": "2023-06-01",
228
+ "Content-Type": "application/json"
229
+ },
230
+ body: JSON.stringify(body)
231
+ });
232
+ if (!res.ok) {
233
+ throw new Error(`Anthropic countTokens failed (${res.status}): ${await res.text()}`);
234
+ }
235
+ const data = await res.json();
236
+ return data.input_tokens;
237
+ }
238
+ function countTokensGroq(messages, model = "llama-3.3-70b-versatile") {
239
+ return countWithTiktoken(messages, model);
240
+ }
241
+ function countTokensMistral(messages, model = "mistral-large-latest") {
242
+ return countWithTiktoken(messages, model);
243
+ }
244
+ function countTokensXai(messages, model = "grok-3") {
245
+ return countWithTiktoken(messages, model);
246
+ }
247
+ function countTokensCerebras(messages, model = "llama-3.3-70b") {
248
+ return countWithTiktoken(messages, model);
249
+ }
250
+ function countTokensOpenRouter(messages, model = "openai/gpt-4o") {
251
+ return countWithTiktoken(messages, model);
252
+ }
253
+ function countTokensOllama(messages, model = "llama3.2") {
254
+ return countWithTiktoken(messages, model);
255
+ }
256
+ var PROVIDER_MODEL_PREFIXES = [
257
+ { prefixes: ["gemini", "imagen", "veo"], provider: "google" },
258
+ { prefixes: ["claude"], provider: "anthropic" },
259
+ { prefixes: ["gpt-", "o1", "o3", "o4", "chatgpt", "dall-e", "gpt-image", "tts-", "whisper", "sora"], provider: "openai" },
260
+ { prefixes: ["grok"], provider: "xai" },
261
+ { prefixes: ["mistral", "mixtral", "codestral", "ministral"], provider: "mistral" },
262
+ { prefixes: ["llama", "gemma", "qwen", "deepseek", "phi"], provider: "groq" }
263
+ ];
264
+ function inferProvider(model) {
265
+ const lower = model.toLowerCase();
266
+ for (const { prefixes, provider } of PROVIDER_MODEL_PREFIXES) {
267
+ for (const prefix of prefixes) {
268
+ if (lower.startsWith(prefix)) return provider;
269
+ }
270
+ }
271
+ return "unknown";
272
+ }
273
+ async function countTokens(options, apiKeys) {
274
+ const model = options.model ?? "gpt-4o";
275
+ const provider = options.provider ?? inferProvider(model);
276
+ if (provider === "google" && apiKeys?.google) {
277
+ const tokens2 = await countTokensGoogle(options.messages, apiKeys.google, model);
278
+ return { tokens: tokens2, model, provider: "google", method: "api" };
279
+ }
280
+ if (provider === "anthropic" && apiKeys?.anthropic) {
281
+ const tokens2 = await countTokensAnthropic(options.messages, apiKeys.anthropic, model);
282
+ return { tokens: tokens2, model, provider: "anthropic", method: "api" };
283
+ }
284
+ const tokens = countWithTiktoken(options.messages, model);
285
+ const resolvedProvider = provider === "unknown" ? "openai" : provider;
286
+ return {
287
+ tokens,
288
+ model,
289
+ provider: resolvedProvider,
290
+ method: provider === "openai" ? "tiktoken" : "tiktoken"
291
+ };
292
+ }
293
+
143
294
  // src/logos.ts
144
295
  var CDN_BASE = "https://blockchainstarter.nyc3.digitaloceanspaces.com/noosphere/logos";
145
296
  var PROVIDER_IDS = [
@@ -2565,6 +2716,36 @@ var OpenAIMediaProvider = class {
2565
2716
  modalities = ["image", "video", "tts", "stt"];
2566
2717
  isLocal = false;
2567
2718
  modelsCache = null;
2719
+ voicesCache = null;
2720
+ /** Auto-fetch available TTS voices by sending an invalid voice and parsing the error. */
2721
+ async fetchVoices() {
2722
+ if (this.voicesCache) return this.voicesCache;
2723
+ try {
2724
+ const res = await fetch(`${OPENAI_API_BASE}/audio/speech`, {
2725
+ method: "POST",
2726
+ headers: {
2727
+ "Content-Type": "application/json",
2728
+ Authorization: `Bearer ${this.apiKey}`
2729
+ },
2730
+ body: JSON.stringify({ model: "tts-1", input: ".", voice: "__discover_voices__" })
2731
+ });
2732
+ if (!res.ok) {
2733
+ const data = await res.json();
2734
+ const msg = data?.error?.message ?? "";
2735
+ const shouldBe = msg.match(/Input should be ([^"]+)/);
2736
+ if (shouldBe) {
2737
+ const voiceList = shouldBe[1].match(/'([a-z]+)'/g);
2738
+ if (voiceList && voiceList.length > 0) {
2739
+ this.voicesCache = voiceList.map((v) => v.replace(/'/g, ""));
2740
+ return this.voicesCache;
2741
+ }
2742
+ }
2743
+ }
2744
+ } catch {
2745
+ }
2746
+ this.voicesCache = [];
2747
+ return this.voicesCache;
2748
+ }
2568
2749
  async ping() {
2569
2750
  try {
2570
2751
  const controller = new AbortController();
@@ -2600,6 +2781,7 @@ var OpenAIMediaProvider = class {
2600
2781
  } finally {
2601
2782
  clearTimeout(timer);
2602
2783
  }
2784
+ const voices = await this.fetchVoices();
2603
2785
  const entries = data?.data ?? [];
2604
2786
  const logo = getProviderLogo("openai");
2605
2787
  const models = [];
@@ -2615,7 +2797,7 @@ var OpenAIMediaProvider = class {
2615
2797
  cost: { price: 0, unit: "per_request" },
2616
2798
  logo,
2617
2799
  description: entry.description,
2618
- capabilities: this.getCapabilities(entry.id, mod)
2800
+ capabilities: this.getCapabilities(entry.id, mod, voices)
2619
2801
  };
2620
2802
  models.push(info);
2621
2803
  }
@@ -2760,7 +2942,7 @@ var OpenAIMediaProvider = class {
2760
2942
  }
2761
2943
  };
2762
2944
  }
2763
- getCapabilities(id, modality) {
2945
+ getCapabilities(id, modality, voices) {
2764
2946
  if (modality === "image") {
2765
2947
  return {
2766
2948
  maxWidth: id.startsWith("dall-e-3") ? 1792 : 1024,
@@ -2769,7 +2951,7 @@ var OpenAIMediaProvider = class {
2769
2951
  }
2770
2952
  if (modality === "tts") {
2771
2953
  return {
2772
- voices: ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"]
2954
+ voices: voices && voices.length > 0 ? voices : void 0
2773
2955
  };
2774
2956
  }
2775
2957
  if (modality === "video") {
@@ -2790,18 +2972,34 @@ var OpenAIMediaProvider = class {
2790
2972
  // src/providers/google-media.ts
2791
2973
  var GOOGLE_API_BASE = "https://generativelanguage.googleapis.com/v1beta";
2792
2974
  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
- ];
2975
+ async function fetchGoogleVoices(apiKey) {
2976
+ try {
2977
+ const res = await fetch(
2978
+ `${GOOGLE_API_BASE}/models/gemini-2.5-flash-preview-tts:generateContent?key=${apiKey}`,
2979
+ {
2980
+ method: "POST",
2981
+ headers: { "Content-Type": "application/json" },
2982
+ body: JSON.stringify({
2983
+ contents: [{ parts: [{ text: "." }] }],
2984
+ generationConfig: {
2985
+ response_modalities: ["AUDIO"],
2986
+ speech_config: { voiceConfig: { prebuiltVoiceConfig: { voiceName: "__discover_voices__" } } }
2987
+ }
2988
+ })
2989
+ }
2990
+ );
2991
+ if (!res.ok) {
2992
+ const data = await res.json();
2993
+ const msg = data?.error?.message ?? "";
2994
+ const match = msg.match(/Allowed voice names are:\s*(.+)/i);
2995
+ if (match) {
2996
+ return match[1].split(",").map((v) => v.trim()).filter(Boolean);
2997
+ }
2998
+ }
2999
+ } catch {
3000
+ }
3001
+ return [];
3002
+ }
2805
3003
  function classifyGoogleModel(model) {
2806
3004
  const name = (model.name ?? "").replace("models/", "");
2807
3005
  const methods = model.supportedGenerationMethods ?? [];
@@ -2819,6 +3017,7 @@ var GoogleMediaProvider = class {
2819
3017
  modalities = ["image", "video", "tts"];
2820
3018
  isLocal = false;
2821
3019
  modelsCache = null;
3020
+ voicesCache = null;
2822
3021
  async ping() {
2823
3022
  try {
2824
3023
  const controller = new AbortController();
@@ -2853,6 +3052,9 @@ var GoogleMediaProvider = class {
2853
3052
  clearTimeout(timer);
2854
3053
  }
2855
3054
  const entries = data?.models ?? [];
3055
+ if (!this.voicesCache) {
3056
+ this.voicesCache = await fetchGoogleVoices(this.apiKey);
3057
+ }
2856
3058
  const logo = getProviderLogo("google");
2857
3059
  const models = [];
2858
3060
  for (const entry of entries) {
@@ -2869,7 +3071,7 @@ var GoogleMediaProvider = class {
2869
3071
  cost: { price: 0, unit: modality2 === "video" ? "per_video" : "per_image" },
2870
3072
  logo,
2871
3073
  description: entry.description,
2872
- capabilities: modality2 === "video" ? { maxDuration: 8, supportsStreaming: false } : modality2 === "tts" ? { voices: GOOGLE_TTS_VOICES } : void 0
3074
+ capabilities: modality2 === "video" ? { maxDuration: 8, supportsStreaming: false } : modality2 === "tts" ? { voices: this.voicesCache && this.voicesCache.length > 0 ? this.voicesCache : void 0 } : void 0
2873
3075
  };
2874
3076
  models.push(info);
2875
3077
  }
@@ -2972,7 +3174,7 @@ var GoogleMediaProvider = class {
2972
3174
  };
2973
3175
  }
2974
3176
  async video(options) {
2975
- const model = options.model ?? "veo-3.0-generate-001";
3177
+ const model = options.model ?? "veo-2.0-generate-001";
2976
3178
  const start = Date.now();
2977
3179
  const body = {
2978
3180
  instances: [{ prompt: options.prompt }],
@@ -3007,10 +3209,15 @@ var GoogleMediaProvider = class {
3007
3209
  if (!pollRes.ok) continue;
3008
3210
  const status = await pollRes.json();
3009
3211
  if (status.done) {
3010
- const videoBase64 = status.response?.generatedSamples?.[0]?.video?.bytesBase64Encoded;
3011
- if (videoBase64) {
3212
+ if (status.error) {
3213
+ throw new Error(`Google video generation error: ${status.error.message ?? JSON.stringify(status.error)}`);
3214
+ }
3215
+ const resp = status.response ?? {};
3216
+ const samples = resp.generateVideoResponse?.generatedSamples ?? resp.generatedSamples ?? [];
3217
+ const video = samples[0]?.video;
3218
+ if (video?.bytesBase64Encoded) {
3012
3219
  return {
3013
- buffer: Buffer.from(videoBase64, "base64"),
3220
+ buffer: Buffer.from(video.bytesBase64Encoded, "base64"),
3014
3221
  provider: "google-media",
3015
3222
  model,
3016
3223
  modality: "video",
@@ -3019,16 +3226,32 @@ var GoogleMediaProvider = class {
3019
3226
  media: { format: "mp4", duration: options.duration }
3020
3227
  };
3021
3228
  }
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
- };
3229
+ if (video?.uri) {
3230
+ const separator = video.uri.includes("?") ? "&" : "?";
3231
+ const videoRes = await fetch(`${video.uri}${separator}key=${this.apiKey}`, { redirect: "follow" });
3232
+ if (videoRes.ok) {
3233
+ const buffer = Buffer.from(await videoRes.arrayBuffer());
3234
+ return {
3235
+ buffer,
3236
+ provider: "google-media",
3237
+ model,
3238
+ modality: "video",
3239
+ latencyMs: Date.now() - start,
3240
+ usage: { cost: 0, unit: "per_video" },
3241
+ media: { format: "mp4", duration: options.duration }
3242
+ };
3243
+ }
3244
+ return {
3245
+ url: video.uri,
3246
+ provider: "google-media",
3247
+ model,
3248
+ modality: "video",
3249
+ latencyMs: Date.now() - start,
3250
+ usage: { cost: 0, unit: "per_video" },
3251
+ media: { format: "mp4", duration: options.duration }
3252
+ };
3253
+ }
3254
+ throw new Error("Google video generation completed but returned no video data");
3032
3255
  }
3033
3256
  }
3034
3257
  throw new Error(`Google video generation timed out after 5 minutes`);
@@ -3225,6 +3448,12 @@ var Noosphere = class {
3225
3448
  getUsage(options) {
3226
3449
  return this.tracker.getSummary(options);
3227
3450
  }
3451
+ async countTokens(options) {
3452
+ const keys = {};
3453
+ if (this.config.keys?.google) keys.google = this.config.keys.google;
3454
+ if (this.config.keys?.anthropic) keys.anthropic = this.config.keys.anthropic;
3455
+ return countTokens(options, keys);
3456
+ }
3228
3457
  // --- Local Model Management ---
3229
3458
  async installModel(name) {
3230
3459
  if (!this.initialized) await this.init();
@@ -3459,6 +3688,12 @@ var Noosphere = class {
3459
3688
  await this.tracker.record(event);
3460
3689
  }
3461
3690
  };
3691
+
3692
+ // src/index.ts
3693
+ var import_pi_ai3 = require("@mariozechner/pi-ai");
3694
+ var import_pi_ai4 = require("@mariozechner/pi-ai");
3695
+ var import_pi_ai5 = require("@mariozechner/pi-ai");
3696
+ var import_pi_ai6 = require("@mariozechner/pi-ai");
3462
3697
  // Annotate the CommonJS export names for ESM import in node:
3463
3698
  0 && (module.exports = {
3464
3699
  AudioCraftProvider,
@@ -3472,8 +3707,29 @@ var Noosphere = class {
3472
3707
  PROVIDER_IDS,
3473
3708
  PROVIDER_LOGOS,
3474
3709
  WhisperLocalProvider,
3710
+ agentLoop,
3711
+ calculateCost,
3712
+ countTokens,
3713
+ countTokensAnthropic,
3714
+ countTokensCerebras,
3715
+ countTokensGoogle,
3716
+ countTokensGroq,
3717
+ countTokensMistral,
3718
+ countTokensOllama,
3719
+ countTokensOpenAI,
3720
+ countTokensOpenRouter,
3721
+ countTokensXai,
3475
3722
  detectOpenAICompatServers,
3476
3723
  getAllProviderLogos,
3477
- getProviderLogo
3724
+ getApiKey,
3725
+ getPiModel,
3726
+ getPiModels,
3727
+ getPiProviders,
3728
+ getProviderLogo,
3729
+ piComplete,
3730
+ piCompleteSimple,
3731
+ piStream,
3732
+ piStreamSimple,
3733
+ setApiKey
3478
3734
  });
3479
3735
  //# sourceMappingURL=index.cjs.map