@elizaos/plugin-local-ai 1.0.0-alpha.2 → 1.0.0-alpha.20

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/dist/index.js CHANGED
@@ -5,411 +5,97 @@ import {
5
5
  } from "./chunk-TIOOHHYI.js";
6
6
 
7
7
  // src/index.ts
8
+ import fs5 from "node:fs";
9
+ import path5 from "node:path";
10
+ import { Readable as Readable2 } from "node:stream";
11
+ import { fileURLToPath } from "node:url";
8
12
  import {
9
- logger as logger10,
10
- ModelTypes as ModelTypes3
13
+ ModelTypes as ModelTypes3,
14
+ logger as logger10
11
15
  } from "@elizaos/core";
12
16
  import { EmbeddingModel, FlagEmbedding } from "fastembed";
13
17
  import {
14
- getLlama as getLlama2,
15
- LlamaChatSession
18
+ LlamaChatSession,
19
+ getLlama as getLlama2
16
20
  } from "node-llama-cpp";
17
- import path5 from "node:path";
18
- import { Readable as Readable2 } from "node:stream";
19
- import { fileURLToPath } from "node:url";
20
- import fs5 from "node:fs";
21
21
 
22
- // src/utils/platform.ts
22
+ // src/environment.ts
23
23
  import { logger } from "@elizaos/core";
24
- import os from "node:os";
25
- import { exec } from "node:child_process";
26
- import { promisify } from "node:util";
27
- var execAsync = promisify(exec);
28
- var PlatformManager = class _PlatformManager {
29
- static instance;
30
- capabilities = null;
31
- constructor() {
32
- }
33
- static getInstance() {
34
- if (!_PlatformManager.instance) {
35
- _PlatformManager.instance = new _PlatformManager();
36
- }
37
- return _PlatformManager.instance;
24
+ import { z } from "zod";
25
+ var configSchema = z.object({
26
+ USE_LOCAL_AI: z.boolean().default(true),
27
+ USE_STUDIOLM_TEXT_MODELS: z.boolean().default(false),
28
+ USE_OLLAMA_TEXT_MODELS: z.boolean().default(false),
29
+ // Ollama Configuration
30
+ OLLAMA_SERVER_URL: z.string().default("http://localhost:11434"),
31
+ OLLAMA_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
32
+ USE_OLLAMA_EMBEDDING: z.boolean().default(false),
33
+ OLLAMA_EMBEDDING_MODEL: z.string().default(""),
34
+ SMALL_OLLAMA_MODEL: z.string().default("deepseek-r1:1.5b"),
35
+ MEDIUM_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
36
+ LARGE_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
37
+ // StudioLM Configuration
38
+ STUDIOLM_SERVER_URL: z.string().default("http://localhost:1234"),
39
+ STUDIOLM_SMALL_MODEL: z.string().default("lmstudio-community/deepseek-r1-distill-qwen-1.5b"),
40
+ STUDIOLM_MEDIUM_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
41
+ STUDIOLM_EMBEDDING_MODEL: z.union([z.boolean(), z.string()]).default(false)
42
+ });
43
+ function validateModelConfig(config) {
44
+ logger.info("Validating model configuration with values:", {
45
+ USE_LOCAL_AI: config.USE_LOCAL_AI,
46
+ USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS,
47
+ USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS
48
+ });
49
+ if (!config.USE_LOCAL_AI) {
50
+ config.USE_LOCAL_AI = true;
51
+ logger.info("Setting USE_LOCAL_AI to true as it's required");
38
52
  }
39
- async initialize() {
40
- try {
41
- logger.info("Initializing platform detection...");
42
- this.capabilities = await this.detectSystemCapabilities();
43
- } catch (error) {
44
- logger.error("Platform detection failed", { error });
45
- throw error;
46
- }
53
+ if (config.USE_STUDIOLM_TEXT_MODELS && config.USE_OLLAMA_TEXT_MODELS) {
54
+ throw new Error(
55
+ "StudioLM and Ollama text models cannot be enabled simultaneously"
56
+ );
47
57
  }
48
- async detectSystemCapabilities() {
49
- const platform = process.platform;
50
- const cpuInfo = this.getCPUInfo();
51
- const gpu = await this.detectGPU();
52
- const supportedBackends = await this.getSupportedBackends(platform, gpu);
53
- const recommendedModelSize = this.getRecommendedModelSize(cpuInfo, gpu);
54
- return {
55
- platform,
56
- cpu: cpuInfo,
57
- gpu,
58
- recommendedModelSize,
59
- supportedBackends
58
+ logger.info("Configuration is valid");
59
+ }
60
+ async function validateConfig(config) {
61
+ try {
62
+ const booleanConfig = {
63
+ USE_LOCAL_AI: true,
64
+ // Always true
65
+ USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS === "true",
66
+ USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS === "true",
67
+ USE_OLLAMA_EMBEDDING: config.USE_OLLAMA_EMBEDDING === "true"
60
68
  };
61
- }
62
- getCPUInfo() {
63
- const cpus = os.cpus();
64
- const totalMemory = os.totalmem();
65
- const freeMemory = os.freemem();
66
- return {
67
- model: cpus[0].model,
68
- cores: cpus.length,
69
- speed: cpus[0].speed,
70
- architecture: process.arch,
71
- memory: {
72
- total: totalMemory,
73
- free: freeMemory
74
- }
69
+ validateModelConfig(booleanConfig);
70
+ const fullConfig = {
71
+ ...booleanConfig,
72
+ OLLAMA_SERVER_URL: config.OLLAMA_SERVER_URL || "http://localhost:11434",
73
+ OLLAMA_MODEL: config.OLLAMA_MODEL || "deepseek-r1-distill-qwen-7b",
74
+ OLLAMA_EMBEDDING_MODEL: config.OLLAMA_EMBEDDING_MODEL || "",
75
+ SMALL_OLLAMA_MODEL: config.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
76
+ MEDIUM_OLLAMA_MODEL: config.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b",
77
+ LARGE_OLLAMA_MODEL: config.LARGE_OLLAMA_MODEL || "deepseek-r1:7b",
78
+ STUDIOLM_SERVER_URL: config.STUDIOLM_SERVER_URL || "http://localhost:1234",
79
+ STUDIOLM_SMALL_MODEL: config.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
80
+ STUDIOLM_MEDIUM_MODEL: config.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b",
81
+ STUDIOLM_EMBEDDING_MODEL: config.STUDIOLM_EMBEDDING_MODEL || false
75
82
  };
76
- }
77
- async detectGPU() {
78
- const platform = process.platform;
79
- try {
80
- switch (platform) {
81
- case "darwin":
82
- return await this.detectMacGPU();
83
- case "win32":
84
- return await this.detectWindowsGPU();
85
- case "linux":
86
- return await this.detectLinuxGPU();
87
- default:
88
- return null;
89
- }
90
- } catch (error) {
91
- logger.error("GPU detection failed", { error });
92
- return null;
93
- }
94
- }
95
- async detectMacGPU() {
96
- try {
97
- const { stdout } = await execAsync("sysctl -n machdep.cpu.brand_string");
98
- const isAppleSilicon = stdout.toLowerCase().includes("apple");
99
- if (isAppleSilicon) {
100
- return {
101
- name: "Apple Silicon",
102
- type: "metal",
103
- isAppleSilicon: true
104
- };
105
- }
106
- const { stdout: gpuInfo } = await execAsync(
107
- "system_profiler SPDisplaysDataType"
108
- );
109
- return {
110
- name: gpuInfo.split("Chipset Model:")[1]?.split("\n")[0]?.trim() || "Unknown GPU",
111
- type: "metal",
112
- isAppleSilicon: false
113
- };
114
- } catch (error) {
115
- logger.error("Mac GPU detection failed", { error });
116
- return {
117
- name: "Unknown Mac GPU",
118
- type: "metal",
119
- isAppleSilicon: false
120
- };
121
- }
122
- }
123
- async detectWindowsGPU() {
124
- try {
125
- const { stdout } = await execAsync(
126
- "wmic path win32_VideoController get name"
127
- );
128
- const gpuName = stdout.split("\n")[1].trim();
129
- if (gpuName.toLowerCase().includes("nvidia")) {
130
- const { stdout: nvidiaInfo } = await execAsync(
131
- "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
132
- );
133
- const [name, memoryStr] = nvidiaInfo.split(",").map((s) => s.trim());
134
- const memory = Number.parseInt(memoryStr);
135
- return {
136
- name,
137
- memory,
138
- type: "cuda",
139
- version: await this.getNvidiaDriverVersion()
140
- };
141
- }
142
- return {
143
- name: gpuName,
144
- type: "directml"
145
- };
146
- } catch (error) {
147
- logger.error("Windows GPU detection failed", { error });
148
- return null;
149
- }
150
- }
151
- async detectLinuxGPU() {
152
- try {
153
- const { stdout } = await execAsync(
154
- "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
155
- );
156
- if (stdout) {
157
- const [name, memoryStr] = stdout.split(",").map((s) => s.trim());
158
- const memory = Number.parseInt(memoryStr);
159
- return {
160
- name,
161
- memory,
162
- type: "cuda",
163
- version: await this.getNvidiaDriverVersion()
164
- };
165
- }
166
- } catch {
167
- try {
168
- const { stdout } = await execAsync("lspci | grep -i vga");
169
- return {
170
- name: stdout.split(":").pop()?.trim() || "Unknown GPU",
171
- type: "none"
172
- };
173
- } catch (error) {
174
- logger.error("Linux GPU detection failed", { error });
175
- return null;
176
- }
177
- }
178
- return null;
179
- }
180
- async getNvidiaDriverVersion() {
181
- try {
182
- const { stdout } = await execAsync(
183
- "nvidia-smi --query-gpu=driver_version --format=csv,noheader"
184
- );
185
- return stdout.trim();
186
- } catch {
187
- return "unknown";
188
- }
189
- }
190
- async getSupportedBackends(platform, gpu) {
191
- const backends = ["cpu"];
192
- if (gpu) {
193
- switch (platform) {
194
- case "darwin":
195
- backends.push("metal");
196
- break;
197
- case "win32":
198
- if (gpu.type === "cuda") {
199
- backends.push("cuda");
200
- }
201
- backends.push("directml");
202
- break;
203
- case "linux":
204
- if (gpu.type === "cuda") {
205
- backends.push("cuda");
206
- }
207
- break;
208
- }
209
- }
210
- return backends;
211
- }
212
- getRecommendedModelSize(cpu, gpu) {
213
- if (gpu?.isAppleSilicon) {
214
- return cpu.memory.total > 16 * 1024 * 1024 * 1024 ? "medium" : "small";
215
- }
216
- if (gpu?.type === "cuda") {
217
- const gpuMemGB = (gpu.memory || 0) / 1024;
218
- if (gpuMemGB >= 16) return "large";
219
- if (gpuMemGB >= 8) return "medium";
220
- }
221
- if (cpu.memory.total > 32 * 1024 * 1024 * 1024) return "medium";
222
- return "small";
223
- }
224
- getCapabilities() {
225
- if (!this.capabilities) {
226
- throw new Error("PlatformManager not initialized");
227
- }
228
- return this.capabilities;
229
- }
230
- isAppleSilicon() {
231
- return !!this.capabilities?.gpu?.isAppleSilicon;
232
- }
233
- hasGPUSupport() {
234
- return !!this.capabilities?.gpu;
235
- }
236
- supportsCUDA() {
237
- return this.capabilities?.gpu?.type === "cuda";
238
- }
239
- supportsMetal() {
240
- return this.capabilities?.gpu?.type === "metal";
241
- }
242
- supportsDirectML() {
243
- return this.capabilities?.gpu?.type === "directml";
244
- }
245
- getRecommendedBackend() {
246
- if (!this.capabilities) {
247
- throw new Error("PlatformManager not initialized");
248
- }
249
- const { gpu, supportedBackends } = this.capabilities;
250
- if (gpu?.type === "cuda") return "cuda";
251
- if (gpu?.type === "metal") return "metal";
252
- if (supportedBackends.includes("directml")) return "directml";
253
- return "cpu";
254
- }
255
- };
256
- var getPlatformManager = () => {
257
- return PlatformManager.getInstance();
258
- };
259
-
260
- // src/utils/tokenizerManager.ts
261
- import {
262
- AutoTokenizer
263
- } from "@huggingface/transformers";
264
- import { logger as logger2 } from "@elizaos/core";
265
- var TokenizerManager = class _TokenizerManager {
266
- static instance = null;
267
- tokenizers;
268
- cacheDir;
269
- modelsDir;
270
- constructor(cacheDir, modelsDir) {
271
- this.tokenizers = /* @__PURE__ */ new Map();
272
- this.cacheDir = cacheDir;
273
- this.modelsDir = modelsDir;
274
- }
275
- static getInstance(cacheDir, modelsDir) {
276
- if (!_TokenizerManager.instance) {
277
- _TokenizerManager.instance = new _TokenizerManager(cacheDir, modelsDir);
278
- }
279
- return _TokenizerManager.instance;
280
- }
281
- async loadTokenizer(modelConfig) {
282
- try {
283
- const tokenizerKey = `${modelConfig.tokenizer.type}-${modelConfig.tokenizer.name}`;
284
- logger2.info("Loading tokenizer:", {
285
- key: tokenizerKey,
286
- name: modelConfig.tokenizer.name,
287
- type: modelConfig.tokenizer.type,
288
- modelsDir: this.modelsDir,
289
- cacheDir: this.cacheDir
290
- });
291
- if (this.tokenizers.has(tokenizerKey)) {
292
- logger2.info("Using cached tokenizer:", { key: tokenizerKey });
293
- const cachedTokenizer = this.tokenizers.get(tokenizerKey);
294
- if (!cachedTokenizer) {
295
- throw new Error(
296
- `Tokenizer ${tokenizerKey} exists in map but returned undefined`
297
- );
298
- }
299
- return cachedTokenizer;
300
- }
301
- const fs6 = await import("node:fs");
302
- if (!fs6.existsSync(this.modelsDir)) {
303
- logger2.warn(
304
- "Models directory does not exist, creating it:",
305
- this.modelsDir
306
- );
307
- fs6.mkdirSync(this.modelsDir, { recursive: true });
308
- }
309
- logger2.info(
310
- "Initializing new tokenizer from HuggingFace with models directory:",
311
- this.modelsDir
312
- );
313
- try {
314
- const tokenizer = await AutoTokenizer.from_pretrained(
315
- modelConfig.tokenizer.name,
316
- {
317
- cache_dir: this.modelsDir,
318
- local_files_only: false
319
- }
320
- );
321
- this.tokenizers.set(tokenizerKey, tokenizer);
322
- logger2.success("Tokenizer loaded successfully:", { key: tokenizerKey });
323
- return tokenizer;
324
- } catch (tokenizeError) {
325
- logger2.error("Failed to load tokenizer from HuggingFace:", {
326
- error: tokenizeError instanceof Error ? tokenizeError.message : String(tokenizeError),
327
- stack: tokenizeError instanceof Error ? tokenizeError.stack : void 0,
328
- tokenizer: modelConfig.tokenizer.name,
329
- modelsDir: this.modelsDir
330
- });
331
- logger2.info("Retrying tokenizer loading...");
332
- const tokenizer = await AutoTokenizer.from_pretrained(
333
- modelConfig.tokenizer.name,
334
- {
335
- cache_dir: this.modelsDir,
336
- local_files_only: false
337
- }
338
- );
339
- this.tokenizers.set(tokenizerKey, tokenizer);
340
- logger2.success("Tokenizer loaded successfully on retry:", {
341
- key: tokenizerKey
342
- });
343
- return tokenizer;
344
- }
345
- } catch (error) {
346
- logger2.error("Failed to load tokenizer:", {
347
- error: error instanceof Error ? error.message : String(error),
348
- stack: error instanceof Error ? error.stack : void 0,
349
- model: modelConfig.name,
350
- tokenizer: modelConfig.tokenizer.name,
351
- modelsDir: this.modelsDir
352
- });
353
- throw error;
354
- }
355
- }
356
- async encode(text, modelConfig) {
357
- try {
358
- logger2.info("Encoding text with tokenizer:", {
359
- length: text.length,
360
- tokenizer: modelConfig.tokenizer.name
361
- });
362
- const tokenizer = await this.loadTokenizer(modelConfig);
363
- logger2.info("Tokenizer loaded, encoding text...");
364
- const encoded = await tokenizer.encode(text, {
365
- add_special_tokens: true,
366
- return_token_type_ids: false
367
- });
368
- logger2.info("Text encoded successfully:", {
369
- tokenCount: encoded.length,
370
- tokenizer: modelConfig.tokenizer.name
371
- });
372
- return encoded;
373
- } catch (error) {
374
- logger2.error("Text encoding failed:", {
375
- error: error instanceof Error ? error.message : String(error),
376
- stack: error instanceof Error ? error.stack : void 0,
377
- textLength: text.length,
378
- tokenizer: modelConfig.tokenizer.name,
379
- modelsDir: this.modelsDir
380
- });
381
- throw error;
382
- }
383
- }
384
- async decode(tokens, modelConfig) {
385
- try {
386
- logger2.info("Decoding tokens with tokenizer:", {
387
- count: tokens.length,
388
- tokenizer: modelConfig.tokenizer.name
389
- });
390
- const tokenizer = await this.loadTokenizer(modelConfig);
391
- logger2.info("Tokenizer loaded, decoding tokens...");
392
- const decoded = await tokenizer.decode(tokens, {
393
- skip_special_tokens: true,
394
- clean_up_tokenization_spaces: true
395
- });
396
- logger2.info("Tokens decoded successfully:", {
397
- textLength: decoded.length,
398
- tokenizer: modelConfig.tokenizer.name
399
- });
400
- return decoded;
401
- } catch (error) {
402
- logger2.error("Token decoding failed:", {
403
- error: error instanceof Error ? error.message : String(error),
404
- stack: error instanceof Error ? error.stack : void 0,
405
- tokenCount: tokens.length,
406
- tokenizer: modelConfig.tokenizer.name,
407
- modelsDir: this.modelsDir
408
- });
409
- throw error;
83
+ const validatedConfig = configSchema.parse(fullConfig);
84
+ return validatedConfig;
85
+ } catch (error) {
86
+ if (error instanceof z.ZodError) {
87
+ const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n");
88
+ logger.error("Zod validation failed:", errorMessages);
89
+ throw new Error(`Configuration validation failed:
90
+ ${errorMessages}`);
410
91
  }
92
+ logger.error("Configuration validation failed:", {
93
+ error: error instanceof Error ? error.message : String(error),
94
+ stack: error instanceof Error ? error.stack : void 0
95
+ });
96
+ throw error;
411
97
  }
412
- };
98
+ }
413
99
 
414
100
  // src/types.ts
415
101
  var MODEL_SPECS = {
@@ -574,52 +260,79 @@ var MODEL_SPECS = {
574
260
  };
575
261
 
576
262
  // src/utils/downloadManager.ts
577
- import { logger as logger3 } from "@elizaos/core";
578
263
  import fs from "node:fs";
579
264
  import https from "node:https";
580
265
  import path from "node:path";
266
+ import { logger as logger2 } from "@elizaos/core";
581
267
  var DownloadManager = class _DownloadManager {
582
268
  static instance = null;
583
269
  cacheDir;
584
270
  modelsDir;
585
271
  // Track active downloads to prevent duplicates
586
272
  activeDownloads = /* @__PURE__ */ new Map();
273
+ /**
274
+ * Creates a new instance of CacheManager.
275
+ *
276
+ * @param {string} cacheDir - The directory path for caching data.
277
+ * @param {string} modelsDir - The directory path for model files.
278
+ */
587
279
  constructor(cacheDir, modelsDir) {
588
280
  this.cacheDir = cacheDir;
589
281
  this.modelsDir = modelsDir;
590
282
  this.ensureCacheDirectory();
591
283
  this.ensureModelsDirectory();
592
284
  }
285
+ /**
286
+ * Returns the singleton instance of the DownloadManager class.
287
+ * If an instance does not already exist, it creates a new one using the provided cache directory and models directory.
288
+ *
289
+ * @param {string} cacheDir - The directory where downloaded files are stored.
290
+ * @param {string} modelsDir - The directory where model files are stored.
291
+ * @returns {DownloadManager} The singleton instance of the DownloadManager class.
292
+ */
593
293
  static getInstance(cacheDir, modelsDir) {
594
294
  if (!_DownloadManager.instance) {
595
295
  _DownloadManager.instance = new _DownloadManager(cacheDir, modelsDir);
596
296
  }
597
297
  return _DownloadManager.instance;
598
298
  }
299
+ /**
300
+ * Ensure that the cache directory exists.
301
+ */
599
302
  ensureCacheDirectory() {
600
- logger3.info("Ensuring cache directory exists:", this.cacheDir);
303
+ logger2.info("Ensuring cache directory exists:", this.cacheDir);
601
304
  if (!fs.existsSync(this.cacheDir)) {
602
305
  fs.mkdirSync(this.cacheDir, { recursive: true });
603
- logger3.info("Created cache directory");
306
+ logger2.info("Created cache directory");
604
307
  }
605
308
  }
309
+ /**
310
+ * Ensure that the models directory exists. If it does not exist, create it.
311
+ */
606
312
  ensureModelsDirectory() {
607
- logger3.info("Ensuring models directory exists:", this.modelsDir);
313
+ logger2.info("Ensuring models directory exists:", this.modelsDir);
608
314
  if (!fs.existsSync(this.modelsDir)) {
609
315
  fs.mkdirSync(this.modelsDir, { recursive: true });
610
- logger3.info("Created models directory");
316
+ logger2.info("Created models directory");
611
317
  }
612
318
  }
319
+ /**
320
+ * Downloads a file from a given URL to a specified destination path asynchronously.
321
+ *
322
+ * @param {string} url - The URL from which to download the file.
323
+ * @param {string} destPath - The destination path where the downloaded file will be saved.
324
+ * @returns {Promise<void>} A Promise that resolves when the file download is completed successfully or rejects if an error occurs.
325
+ */
613
326
  async downloadFileInternal(url, destPath) {
614
327
  return new Promise((resolve, reject) => {
615
- logger3.info(`Starting download to: ${destPath}`);
328
+ logger2.info(`Starting download to: ${destPath}`);
616
329
  const tempPath = `${destPath}.tmp`;
617
330
  if (fs.existsSync(tempPath)) {
618
331
  try {
619
- logger3.warn(`Removing existing temporary file: ${tempPath}`);
332
+ logger2.warn(`Removing existing temporary file: ${tempPath}`);
620
333
  fs.unlinkSync(tempPath);
621
334
  } catch (err) {
622
- logger3.error(
335
+ logger2.error(
623
336
  `Failed to remove existing temporary file: ${err instanceof Error ? err.message : String(err)}`
624
337
  );
625
338
  }
@@ -656,7 +369,7 @@ var DownloadManager = class _DownloadManager {
656
369
  let lastLoggedPercent = 0;
657
370
  const barLength = 30;
658
371
  const fileName = path.basename(destPath);
659
- logger3.info(`Downloading ${fileName}: ${"\u25B1".repeat(barLength)} 0%`);
372
+ logger2.info(`Downloading ${fileName}: ${"\u25B1".repeat(barLength)} 0%`);
660
373
  const file = fs.createWriteStream(tempPath);
661
374
  response.on("data", (chunk) => {
662
375
  downloadedSize += chunk.length;
@@ -666,7 +379,7 @@ var DownloadManager = class _DownloadManager {
666
379
  downloadedSize / totalSize * barLength
667
380
  );
668
381
  const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
669
- logger3.info(
382
+ logger2.info(
670
383
  `Downloading ${fileName}: ${progressBar} ${percent}%`
671
384
  );
672
385
  lastLoggedPercent = percent;
@@ -677,7 +390,7 @@ var DownloadManager = class _DownloadManager {
677
390
  file.close(() => {
678
391
  try {
679
392
  const completedBar = "\u25B0".repeat(barLength);
680
- logger3.info(`Downloading ${fileName}: ${completedBar} 100%`);
393
+ logger2.info(`Downloading ${fileName}: ${completedBar} 100%`);
681
394
  const destDir = path.dirname(destPath);
682
395
  if (!fs.existsSync(destDir)) {
683
396
  fs.mkdirSync(destDir, { recursive: true });
@@ -692,29 +405,29 @@ var DownloadManager = class _DownloadManager {
692
405
  try {
693
406
  const backupPath = `${destPath}.bak`;
694
407
  fs.renameSync(destPath, backupPath);
695
- logger3.info(
408
+ logger2.info(
696
409
  `Created backup of existing file: ${backupPath}`
697
410
  );
698
411
  fs.renameSync(tempPath, destPath);
699
412
  if (fs.existsSync(backupPath)) {
700
413
  fs.unlinkSync(backupPath);
701
- logger3.info(
414
+ logger2.info(
702
415
  `Removed backup file after successful update: ${backupPath}`
703
416
  );
704
417
  }
705
418
  } catch (moveErr) {
706
- logger3.error(
419
+ logger2.error(
707
420
  `Error replacing file: ${moveErr instanceof Error ? moveErr.message : String(moveErr)}`
708
421
  );
709
422
  const backupPath = `${destPath}.bak`;
710
423
  if (fs.existsSync(backupPath)) {
711
424
  try {
712
425
  fs.renameSync(backupPath, destPath);
713
- logger3.info(
426
+ logger2.info(
714
427
  `Restored from backup after failed update: ${backupPath}`
715
428
  );
716
429
  } catch (restoreErr) {
717
- logger3.error(
430
+ logger2.error(
718
431
  `Failed to restore from backup: ${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)}`
719
432
  );
720
433
  }
@@ -723,7 +436,7 @@ var DownloadManager = class _DownloadManager {
723
436
  try {
724
437
  fs.unlinkSync(tempPath);
725
438
  } catch (unlinkErr) {
726
- logger3.error(
439
+ logger2.error(
727
440
  `Failed to clean up temp file: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
728
441
  );
729
442
  }
@@ -734,20 +447,20 @@ var DownloadManager = class _DownloadManager {
734
447
  } else {
735
448
  fs.renameSync(tempPath, destPath);
736
449
  }
737
- logger3.success(
450
+ logger2.success(
738
451
  `Download of ${fileName} completed successfully`
739
452
  );
740
453
  this.activeDownloads.delete(destPath);
741
454
  resolve();
742
455
  } catch (err) {
743
- logger3.error(
456
+ logger2.error(
744
457
  `Error finalizing download: ${err instanceof Error ? err.message : String(err)}`
745
458
  );
746
459
  if (fs.existsSync(tempPath)) {
747
460
  try {
748
461
  fs.unlinkSync(tempPath);
749
462
  } catch (unlinkErr) {
750
- logger3.error(
463
+ logger2.error(
751
464
  `Failed to clean up temp file: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
752
465
  );
753
466
  }
@@ -758,7 +471,7 @@ var DownloadManager = class _DownloadManager {
758
471
  });
759
472
  });
760
473
  file.on("error", (err) => {
761
- logger3.error(
474
+ logger2.error(
762
475
  `File write error: ${err instanceof Error ? err.message : String(err)}`
763
476
  );
764
477
  file.close(() => {
@@ -766,7 +479,7 @@ var DownloadManager = class _DownloadManager {
766
479
  try {
767
480
  fs.unlinkSync(tempPath);
768
481
  } catch (unlinkErr) {
769
- logger3.error(
482
+ logger2.error(
770
483
  `Failed to clean up temp file after error: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
771
484
  );
772
485
  }
@@ -778,14 +491,14 @@ var DownloadManager = class _DownloadManager {
778
491
  }
779
492
  );
780
493
  request.on("error", (err) => {
781
- logger3.error(
494
+ logger2.error(
782
495
  `Request error: ${err instanceof Error ? err.message : String(err)}`
783
496
  );
784
497
  if (fs.existsSync(tempPath)) {
785
498
  try {
786
499
  fs.unlinkSync(tempPath);
787
500
  } catch (unlinkErr) {
788
- logger3.error(
501
+ logger2.error(
789
502
  `Failed to clean up temp file after request error: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
790
503
  );
791
504
  }
@@ -794,13 +507,13 @@ var DownloadManager = class _DownloadManager {
794
507
  reject(err);
795
508
  });
796
509
  request.on("timeout", () => {
797
- logger3.error("Download timeout occurred");
510
+ logger2.error("Download timeout occurred");
798
511
  request.destroy();
799
512
  if (fs.existsSync(tempPath)) {
800
513
  try {
801
514
  fs.unlinkSync(tempPath);
802
515
  } catch (unlinkErr) {
803
- logger3.error(
516
+ logger2.error(
804
517
  `Failed to clean up temp file after timeout: ${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}`
805
518
  );
806
519
  }
@@ -810,16 +523,23 @@ var DownloadManager = class _DownloadManager {
810
523
  });
811
524
  });
812
525
  }
526
+ /**
527
+ * Asynchronously downloads a file from the specified URL to the destination path.
528
+ *
529
+ * @param {string} url - The URL of the file to download.
530
+ * @param {string} destPath - The destination path to save the downloaded file.
531
+ * @returns {Promise<void>} A Promise that resolves once the file has been successfully downloaded.
532
+ */
813
533
  async downloadFile(url, destPath) {
814
534
  if (this.activeDownloads.has(destPath)) {
815
- logger3.info(
535
+ logger2.info(
816
536
  `Download for ${destPath} already in progress, waiting for it to complete...`
817
537
  );
818
538
  const existingDownload = this.activeDownloads.get(destPath);
819
539
  if (existingDownload) {
820
540
  return existingDownload;
821
541
  }
822
- logger3.warn(
542
+ logger2.warn(
823
543
  `Download for ${destPath} was marked as in progress but not found in tracking map`
824
544
  );
825
545
  }
@@ -832,12 +552,20 @@ var DownloadManager = class _DownloadManager {
832
552
  throw error;
833
553
  }
834
554
  }
555
+ /**
556
+ * Downloads a model specified by the modelSpec and saves it to the provided modelPath.
557
+ * If the model is successfully downloaded, returns true, otherwise returns false.
558
+ *
559
+ * @param {ModelSpec} modelSpec - The model specification containing repo and name.
560
+ * @param {string} modelPath - The path where the model will be saved.
561
+ * @returns {Promise<boolean>} - Indicates if the model was successfully downloaded or not.
562
+ */
835
563
  async downloadModel(modelSpec, modelPath) {
836
564
  try {
837
- logger3.info("Starting local model download...");
565
+ logger2.info("Starting local model download...");
838
566
  const modelDir = path.dirname(modelPath);
839
567
  if (!fs.existsSync(modelDir)) {
840
- logger3.info("Creating model directory:", modelDir);
568
+ logger2.info("Creating model directory:", modelDir);
841
569
  fs.mkdirSync(modelDir, { recursive: true });
842
570
  }
843
571
  if (!fs.existsSync(modelPath)) {
@@ -863,20 +591,20 @@ var DownloadManager = class _DownloadManager {
863
591
  let downloadSuccess = false;
864
592
  for (const attempt of attempts) {
865
593
  try {
866
- logger3.info("Attempting model download:", {
594
+ logger2.info("Attempting model download:", {
867
595
  description: attempt.description,
868
596
  url: attempt.url,
869
597
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
870
598
  });
871
599
  await this.downloadFile(attempt.url, modelPath);
872
- logger3.success(
600
+ logger2.success(
873
601
  `Model download complete: ${modelSpec.name} using ${attempt.description}`
874
602
  );
875
603
  downloadSuccess = true;
876
604
  break;
877
605
  } catch (error) {
878
606
  lastError = error;
879
- logger3.warn("Model download attempt failed:", {
607
+ logger2.warn("Model download attempt failed:", {
880
608
  description: attempt.description,
881
609
  error: error instanceof Error ? error.message : String(error),
882
610
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -888,10 +616,10 @@ var DownloadManager = class _DownloadManager {
888
616
  }
889
617
  return true;
890
618
  }
891
- logger3.info("Model already exists at:", modelPath);
619
+ logger2.info("Model already exists at:", modelPath);
892
620
  return false;
893
621
  } catch (error) {
894
- logger3.error("Model download failed:", {
622
+ logger2.error("Model download failed:", {
895
623
  error: error instanceof Error ? error.message : String(error),
896
624
  modelPath,
897
625
  model: modelSpec.name
@@ -899,22 +627,39 @@ var DownloadManager = class _DownloadManager {
899
627
  throw error;
900
628
  }
901
629
  }
630
+ /**
631
+ * Returns the cache directory path.
632
+ *
633
+ * @returns {string} The path of the cache directory.
634
+ */
902
635
  getCacheDir() {
903
636
  return this.cacheDir;
904
637
  }
638
+ /**
639
+ * Downloads a file from a given URL to a specified destination path.
640
+ *
641
+ * @param {string} url - The URL of the file to download.
642
+ * @param {string} destPath - The destination path where the file should be saved.
643
+ * @returns {Promise<void>} A Promise that resolves once the file has been downloaded.
644
+ */
905
645
  async downloadFromUrl(url, destPath) {
906
646
  return this.downloadFile(url, destPath);
907
647
  }
648
+ /**
649
+ * Ensures that the specified directory exists. If it does not exist, it will be created.
650
+ * @param {string} dirPath - The path of the directory to ensure existence of.
651
+ * @returns {void}
652
+ */
908
653
  ensureDirectoryExists(dirPath) {
909
654
  if (!fs.existsSync(dirPath)) {
910
655
  fs.mkdirSync(dirPath, { recursive: true });
911
- logger3.info(`Created directory: ${dirPath}`);
656
+ logger2.info(`Created directory: ${dirPath}`);
912
657
  }
913
658
  }
914
659
  };
915
660
 
916
661
  // src/utils/ollamaManager.ts
917
- import { logger as logger4, ModelTypes } from "@elizaos/core";
662
+ import { ModelTypes, logger as logger3 } from "@elizaos/core";
918
663
 
919
664
  // ../../node_modules/node-fetch/src/index.js
920
665
  import http2 from "node:http";
@@ -964,7 +709,7 @@ var dist_default = dataUriToBuffer;
964
709
 
965
710
  // ../../node_modules/node-fetch/src/body.js
966
711
  import Stream, { PassThrough } from "node:stream";
967
- import { types, deprecate, promisify as promisify2 } from "node:util";
712
+ import { types, deprecate, promisify } from "node:util";
968
713
  import { Buffer as Buffer2 } from "node:buffer";
969
714
 
970
715
  // ../../node_modules/node-fetch/src/errors/base.js
@@ -1021,7 +766,7 @@ var isSameProtocol = (destination, original) => {
1021
766
  };
1022
767
 
1023
768
  // ../../node_modules/node-fetch/src/body.js
1024
- var pipeline = promisify2(Stream.pipeline);
769
+ var pipeline = promisify(Stream.pipeline);
1025
770
  var INTERNALS = Symbol("Body internals");
1026
771
  var Body = class {
1027
772
  constructor(body, {
@@ -2203,20 +1948,32 @@ var OllamaManager = class _OllamaManager {
2203
1948
  small: process.env.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
2204
1949
  medium: process.env.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b"
2205
1950
  };
1951
+ /**
1952
+ * Private constructor for initializing OllamaManager.
1953
+ */
2206
1954
  constructor() {
2207
1955
  this.serverUrl = process.env.OLLAMA_SERVER_URL || "http://localhost:11434";
2208
- logger4.info("OllamaManager initialized with configuration:", {
1956
+ logger3.info("OllamaManager initialized with configuration:", {
2209
1957
  serverUrl: this.serverUrl,
2210
1958
  configuredModels: this.configuredModels,
2211
1959
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2212
1960
  });
2213
1961
  }
1962
+ /**
1963
+ * Returns an instance of the OllamaManager class.
1964
+ * If an instance does not already exist, a new instance is created and returned.
1965
+ * @returns {OllamaManager} The instance of the OllamaManager class.
1966
+ */
2214
1967
  static getInstance() {
2215
1968
  if (!_OllamaManager.instance) {
2216
1969
  _OllamaManager.instance = new _OllamaManager();
2217
1970
  }
2218
1971
  return _OllamaManager.instance;
2219
1972
  }
1973
+ /**
1974
+ * Asynchronously checks the status of the server by attempting to fetch the "/api/tags" endpoint.
1975
+ * @returns A Promise that resolves to a boolean indicating if the server is reachable and responding with a successful status.
1976
+ */
2220
1977
  async checkServerStatus() {
2221
1978
  try {
2222
1979
  const response = await fetch2(`${this.serverUrl}/api/tags`);
@@ -2225,7 +1982,7 @@ var OllamaManager = class _OllamaManager {
2225
1982
  }
2226
1983
  return true;
2227
1984
  } catch (error) {
2228
- logger4.error("Ollama server check failed:", {
1985
+ logger3.error("Ollama server check failed:", {
2229
1986
  error: error instanceof Error ? error.message : String(error),
2230
1987
  serverUrl: this.serverUrl,
2231
1988
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2233,6 +1990,11 @@ var OllamaManager = class _OllamaManager {
2233
1990
  return false;
2234
1991
  }
2235
1992
  }
1993
+ /**
1994
+ * Fetches the available Ollama models from the specified server URL.
1995
+ *
1996
+ * @returns {Promise<void>} A Promise that resolves when the available models are successfully fetched.
1997
+ */
2236
1998
  async fetchAvailableModels() {
2237
1999
  try {
2238
2000
  const response = await fetch2(`${this.serverUrl}/api/tags`);
@@ -2241,13 +2003,13 @@ var OllamaManager = class _OllamaManager {
2241
2003
  }
2242
2004
  const data = await response.json();
2243
2005
  this.availableModels = data.models;
2244
- logger4.info("Ollama available models:", {
2006
+ logger3.info("Ollama available models:", {
2245
2007
  count: this.availableModels.length,
2246
2008
  models: this.availableModels.map((m) => m.name),
2247
2009
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2248
2010
  });
2249
2011
  } catch (error) {
2250
- logger4.error("Failed to fetch Ollama models:", {
2012
+ logger3.error("Failed to fetch Ollama models:", {
2251
2013
  error: error instanceof Error ? error.message : String(error),
2252
2014
  serverUrl: this.serverUrl,
2253
2015
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2255,6 +2017,12 @@ var OllamaManager = class _OllamaManager {
2255
2017
  throw error;
2256
2018
  }
2257
2019
  }
2020
+ /**
2021
+ * Asynchronously tests a model specified by the given modelId.
2022
+ *
2023
+ * @param {string} modelId - The ID of the model to be tested.
2024
+ * @returns {Promise<boolean>} - A promise that resolves to true if the model test is successful, false otherwise.
2025
+ */
2258
2026
  async testModel(modelId) {
2259
2027
  try {
2260
2028
  const testRequest = {
@@ -2266,7 +2034,7 @@ var OllamaManager = class _OllamaManager {
2266
2034
  num_predict: 100
2267
2035
  }
2268
2036
  };
2269
- logger4.info(`Testing model ${modelId}...`);
2037
+ logger3.info(`Testing model ${modelId}...`);
2270
2038
  const response = await fetch2(`${this.serverUrl}/api/generate`, {
2271
2039
  method: "POST",
2272
2040
  headers: {
@@ -2281,14 +2049,14 @@ var OllamaManager = class _OllamaManager {
2281
2049
  if (!result.response) {
2282
2050
  throw new Error("No valid response content received");
2283
2051
  }
2284
- logger4.info(`Model ${modelId} test response:`, {
2052
+ logger3.info(`Model ${modelId} test response:`, {
2285
2053
  content: result.response,
2286
2054
  model: result.model,
2287
2055
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2288
2056
  });
2289
2057
  return true;
2290
2058
  } catch (error) {
2291
- logger4.error(`Model ${modelId} test failed:`, {
2059
+ logger3.error(`Model ${modelId} test failed:`, {
2292
2060
  error: error instanceof Error ? error.message : String(error),
2293
2061
  stack: error instanceof Error ? error.stack : void 0,
2294
2062
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2296,8 +2064,13 @@ var OllamaManager = class _OllamaManager {
2296
2064
  return false;
2297
2065
  }
2298
2066
  }
2067
+ /**
2068
+ * Asynchronously tests the configured text models to ensure they are working properly.
2069
+ * Logs the test results for each model and outputs a warning if any models fail the test.
2070
+ * @returns {Promise<void>} A Promise that resolves when all configured models have been tested.
2071
+ */
2299
2072
  async testTextModels() {
2300
- logger4.info("Testing configured text models...");
2073
+ logger3.info("Testing configured text models...");
2301
2074
  const results = await Promise.all([
2302
2075
  this.testModel(this.configuredModels.small),
2303
2076
  this.testModel(this.configuredModels.medium)
@@ -2307,22 +2080,28 @@ var OllamaManager = class _OllamaManager {
2307
2080
  const failedModels = [];
2308
2081
  if (!smallWorking) failedModels.push("small");
2309
2082
  if (!mediumWorking) failedModels.push("medium");
2310
- logger4.warn("Some models failed the test:", {
2083
+ logger3.warn("Some models failed the test:", {
2311
2084
  failedModels,
2312
2085
  small: this.configuredModels.small,
2313
2086
  medium: this.configuredModels.medium
2314
2087
  });
2315
2088
  } else {
2316
- logger4.success("All configured models passed the test");
2089
+ logger3.success("All configured models passed the test");
2317
2090
  }
2318
2091
  }
2092
+ /**
2093
+ * Asynchronously initializes the Ollama service by checking server status,
2094
+ * fetching available models, and testing text models.
2095
+ *
2096
+ * @returns A Promise that resolves when initialization is complete
2097
+ */
2319
2098
  async initialize() {
2320
2099
  try {
2321
2100
  if (this.initialized) {
2322
- logger4.info("Ollama already initialized, skipping initialization");
2101
+ logger3.info("Ollama already initialized, skipping initialization");
2323
2102
  return;
2324
2103
  }
2325
- logger4.info("Starting Ollama initialization...");
2104
+ logger3.info("Starting Ollama initialization...");
2326
2105
  const serverAvailable = await this.checkServerStatus();
2327
2106
  if (!serverAvailable) {
2328
2107
  throw new Error("Ollama server is not available");
@@ -2330,24 +2109,40 @@ var OllamaManager = class _OllamaManager {
2330
2109
  await this.fetchAvailableModels();
2331
2110
  await this.testTextModels();
2332
2111
  this.initialized = true;
2333
- logger4.success("Ollama initialization complete");
2112
+ logger3.success("Ollama initialization complete");
2334
2113
  } catch (error) {
2335
- logger4.error("Ollama initialization failed:", {
2114
+ logger3.error("Ollama initialization failed:", {
2336
2115
  error: error instanceof Error ? error.message : String(error),
2337
2116
  stack: error instanceof Error ? error.stack : void 0
2338
2117
  });
2339
2118
  throw error;
2340
2119
  }
2341
2120
  }
2121
+ /**
2122
+ * Retrieves the available Ollama models.
2123
+ *
2124
+ * @returns {OllamaModel[]} An array of OllamaModel objects representing the available models.
2125
+ */
2342
2126
  getAvailableModels() {
2343
2127
  return this.availableModels;
2344
2128
  }
2129
+ /**
2130
+ * Check if the object is initialized.
2131
+ * @returns {boolean} True if the object is initialized, false otherwise.
2132
+ */
2345
2133
  isInitialized() {
2346
2134
  return this.initialized;
2347
2135
  }
2136
+ /**
2137
+ * Generates text using the Ollama AI model.
2138
+ *
2139
+ * @param {GenerateTextParams} params - The parameters for generating text.
2140
+ * @param {boolean} [isInitialized=false] - Flag indicating if Ollama is already initialized.
2141
+ * @returns {Promise<string>} - A promise that resolves with the generated text.
2142
+ */
2348
2143
  async generateText(params, isInitialized = false) {
2349
2144
  try {
2350
- logger4.info("Ollama generateText entry:", {
2145
+ logger3.info("Ollama generateText entry:", {
2351
2146
  isInitialized,
2352
2147
  currentInitState: this.initialized,
2353
2148
  managerInitState: this.isInitialized(),
@@ -2360,7 +2155,7 @@ var OllamaManager = class _OllamaManager {
2360
2155
  "Ollama not initialized. Please initialize before generating text."
2361
2156
  );
2362
2157
  }
2363
- logger4.info("Ollama preparing request:", {
2158
+ logger3.info("Ollama preparing request:", {
2364
2159
  model: params.modelType === ModelTypes.TEXT_LARGE ? this.configuredModels.medium : this.configuredModels.small,
2365
2160
  contextLength: params.prompt.length,
2366
2161
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2393,24 +2188,24 @@ var OllamaManager = class _OllamaManager {
2393
2188
  throw new Error("No valid response content received from Ollama");
2394
2189
  }
2395
2190
  let responseText = result.response;
2396
- logger4.info("Raw response structure:", {
2191
+ logger3.info("Raw response structure:", {
2397
2192
  responseLength: responseText.length,
2398
2193
  hasAction: responseText.includes("action"),
2399
2194
  hasThinkTag: responseText.includes("<think>")
2400
2195
  });
2401
2196
  if (responseText.includes("<think>")) {
2402
- logger4.info("Cleaning think tags from response");
2197
+ logger3.info("Cleaning think tags from response");
2403
2198
  responseText = responseText.replace(/<think>[\s\S]*?<\/think>\n?/g, "");
2404
- logger4.info("Think tags removed from response");
2199
+ logger3.info("Think tags removed from response");
2405
2200
  }
2406
- logger4.info("Ollama request completed successfully:", {
2201
+ logger3.info("Ollama request completed successfully:", {
2407
2202
  responseLength: responseText.length,
2408
2203
  hasThinkTags: responseText.includes("<think>"),
2409
2204
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2410
2205
  });
2411
2206
  return responseText;
2412
2207
  } catch (error) {
2413
- logger4.error("Ollama text generation error:", {
2208
+ logger3.error("Ollama text generation error:", {
2414
2209
  error: error instanceof Error ? error.message : String(error),
2415
2210
  stack: error instanceof Error ? error.stack : void 0,
2416
2211
  phase: "text generation",
@@ -2421,8 +2216,341 @@ var OllamaManager = class _OllamaManager {
2421
2216
  }
2422
2217
  };
2423
2218
 
2219
+ // src/utils/platform.ts
2220
+ import { exec } from "node:child_process";
2221
+ import os from "node:os";
2222
+ import { promisify as promisify2 } from "node:util";
2223
+ import { logger as logger4 } from "@elizaos/core";
2224
+ var execAsync = promisify2(exec);
2225
+ var PlatformManager = class _PlatformManager {
2226
+ static instance;
2227
+ capabilities = null;
2228
+ /**
2229
+ * Private constructor method.
2230
+ */
2231
+ constructor() {
2232
+ }
2233
+ /**
2234
+ * Get the singleton instance of the PlatformManager class
2235
+ * @returns {PlatformManager} The instance of PlatformManager
2236
+ */
2237
+ static getInstance() {
2238
+ if (!_PlatformManager.instance) {
2239
+ _PlatformManager.instance = new _PlatformManager();
2240
+ }
2241
+ return _PlatformManager.instance;
2242
+ }
2243
+ /**
2244
+ * Asynchronous method to initialize platform detection.
2245
+ *
2246
+ * @returns {Promise<void>} Promise that resolves once platform detection is completed.
2247
+ */
2248
+ async initialize() {
2249
+ try {
2250
+ logger4.info("Initializing platform detection...");
2251
+ this.capabilities = await this.detectSystemCapabilities();
2252
+ } catch (error) {
2253
+ logger4.error("Platform detection failed", { error });
2254
+ throw error;
2255
+ }
2256
+ }
2257
+ /**
2258
+ * Detects the system capabilities including platform, CPU information, GPU information,
2259
+ * supported backends, and recommended model size.
2260
+ *
2261
+ * @returns {Promise<SystemCapabilities>} Details of the system capabilities including platform, CPU info, GPU info,
2262
+ * recommended model size, and supported backends.
2263
+ */
2264
+ async detectSystemCapabilities() {
2265
+ const platform = process.platform;
2266
+ const cpuInfo = this.getCPUInfo();
2267
+ const gpu = await this.detectGPU();
2268
+ const supportedBackends = await this.getSupportedBackends(platform, gpu);
2269
+ const recommendedModelSize = this.getRecommendedModelSize(cpuInfo, gpu);
2270
+ return {
2271
+ platform,
2272
+ cpu: cpuInfo,
2273
+ gpu,
2274
+ recommendedModelSize,
2275
+ supportedBackends
2276
+ };
2277
+ }
2278
+ /**
2279
+ * Returns information about the CPU and memory of the system.
2280
+ * @returns {SystemCPU} The CPU information including model, number of cores, speed, architecture, and memory details.
2281
+ */
2282
+ getCPUInfo() {
2283
+ const cpus = os.cpus();
2284
+ const totalMemory = os.totalmem();
2285
+ const freeMemory = os.freemem();
2286
+ return {
2287
+ model: cpus[0].model,
2288
+ cores: cpus.length,
2289
+ speed: cpus[0].speed,
2290
+ architecture: process.arch,
2291
+ memory: {
2292
+ total: totalMemory,
2293
+ free: freeMemory
2294
+ }
2295
+ };
2296
+ }
2297
+ /**
2298
+ * Asynchronously detects the GPU information based on the current platform.
2299
+ * @returns A promise that resolves with the GPU information if detection is successful, otherwise null.
2300
+ */
2301
+ async detectGPU() {
2302
+ const platform = process.platform;
2303
+ try {
2304
+ switch (platform) {
2305
+ case "darwin":
2306
+ return await this.detectMacGPU();
2307
+ case "win32":
2308
+ return await this.detectWindowsGPU();
2309
+ case "linux":
2310
+ return await this.detectLinuxGPU();
2311
+ default:
2312
+ return null;
2313
+ }
2314
+ } catch (error) {
2315
+ logger4.error("GPU detection failed", { error });
2316
+ return null;
2317
+ }
2318
+ }
2319
+ /**
2320
+ * Asynchronously detects the GPU of a Mac system.
2321
+ * @returns {Promise<SystemGPU>} A promise that resolves to an object representing the detected GPU.
2322
+ */
2323
+ async detectMacGPU() {
2324
+ try {
2325
+ const { stdout } = await execAsync("sysctl -n machdep.cpu.brand_string");
2326
+ const isAppleSilicon = stdout.toLowerCase().includes("apple");
2327
+ if (isAppleSilicon) {
2328
+ return {
2329
+ name: "Apple Silicon",
2330
+ type: "metal",
2331
+ isAppleSilicon: true
2332
+ };
2333
+ }
2334
+ const { stdout: gpuInfo } = await execAsync(
2335
+ "system_profiler SPDisplaysDataType"
2336
+ );
2337
+ return {
2338
+ name: gpuInfo.split("Chipset Model:")[1]?.split("\n")[0]?.trim() || "Unknown GPU",
2339
+ type: "metal",
2340
+ isAppleSilicon: false
2341
+ };
2342
+ } catch (error) {
2343
+ logger4.error("Mac GPU detection failed", { error });
2344
+ return {
2345
+ name: "Unknown Mac GPU",
2346
+ type: "metal",
2347
+ isAppleSilicon: false
2348
+ };
2349
+ }
2350
+ }
2351
+ /**
2352
+ * Detects the GPU in a Windows system and returns information about it.
2353
+ *
2354
+ * @returns {Promise<SystemGPU | null>} A promise that resolves with the detected GPU information or null if detection fails.
2355
+ */
2356
+ async detectWindowsGPU() {
2357
+ try {
2358
+ const { stdout } = await execAsync(
2359
+ "wmic path win32_VideoController get name"
2360
+ );
2361
+ const gpuName = stdout.split("\n")[1].trim();
2362
+ if (gpuName.toLowerCase().includes("nvidia")) {
2363
+ const { stdout: nvidiaInfo } = await execAsync(
2364
+ "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
2365
+ );
2366
+ const [name, memoryStr] = nvidiaInfo.split(",").map((s) => s.trim());
2367
+ const memory = Number.parseInt(memoryStr);
2368
+ return {
2369
+ name,
2370
+ memory,
2371
+ type: "cuda",
2372
+ version: await this.getNvidiaDriverVersion()
2373
+ };
2374
+ }
2375
+ return {
2376
+ name: gpuName,
2377
+ type: "directml"
2378
+ };
2379
+ } catch (error) {
2380
+ logger4.error("Windows GPU detection failed", { error });
2381
+ return null;
2382
+ }
2383
+ }
2384
+ /**
2385
+ * Asynchronously detects the GPU information for Linux systems.
2386
+ * Tries to detect NVIDIA GPU first using 'nvidia-smi' command and if successful,
2387
+ * returns the GPU name, memory size, type as 'cuda', and NVIDIA driver version.
2388
+ * If NVIDIA detection fails, it falls back to checking for other GPUs using 'lspci | grep -i vga' command.
2389
+ * If no GPU is detected, it returns null.
2390
+ *
2391
+ * @returns {Promise<SystemGPU | null>} The detected GPU information or null if detection fails.
2392
+ */
2393
+ async detectLinuxGPU() {
2394
+ try {
2395
+ const { stdout } = await execAsync(
2396
+ "nvidia-smi --query-gpu=name,memory.total --format=csv,noheader"
2397
+ );
2398
+ if (stdout) {
2399
+ const [name, memoryStr] = stdout.split(",").map((s) => s.trim());
2400
+ const memory = Number.parseInt(memoryStr);
2401
+ return {
2402
+ name,
2403
+ memory,
2404
+ type: "cuda",
2405
+ version: await this.getNvidiaDriverVersion()
2406
+ };
2407
+ }
2408
+ } catch {
2409
+ try {
2410
+ const { stdout } = await execAsync("lspci | grep -i vga");
2411
+ return {
2412
+ name: stdout.split(":").pop()?.trim() || "Unknown GPU",
2413
+ type: "none"
2414
+ };
2415
+ } catch (error) {
2416
+ logger4.error("Linux GPU detection failed", { error });
2417
+ return null;
2418
+ }
2419
+ }
2420
+ return null;
2421
+ }
2422
+ /**
2423
+ * Asynchronously retrieves the driver version of the Nvidia GPU using the 'nvidia-smi' command.
2424
+ *
2425
+ * @returns A promise that resolves with the driver version as a string, or 'unknown' if an error occurs.
2426
+ */
2427
+ async getNvidiaDriverVersion() {
2428
+ try {
2429
+ const { stdout } = await execAsync(
2430
+ "nvidia-smi --query-gpu=driver_version --format=csv,noheader"
2431
+ );
2432
+ return stdout.trim();
2433
+ } catch {
2434
+ return "unknown";
2435
+ }
2436
+ }
2437
+ /**
2438
+ * Retrieves the supported backends based on the platform and GPU type.
2439
+ * @param {NodeJS.Platform} platform - The platform on which the code is running.
2440
+ * @param {SystemGPU | null} gpu - The GPU information, if available.
2441
+ * @returns {Promise<Array<"cuda" | "metal" | "directml" | "cpu">>} - An array of supported backends including 'cuda', 'metal', 'directml', and 'cpu'.
2442
+ */
2443
+ async getSupportedBackends(platform, gpu) {
2444
+ const backends = ["cpu"];
2445
+ if (gpu) {
2446
+ switch (platform) {
2447
+ case "darwin":
2448
+ backends.push("metal");
2449
+ break;
2450
+ case "win32":
2451
+ if (gpu.type === "cuda") {
2452
+ backends.push("cuda");
2453
+ }
2454
+ backends.push("directml");
2455
+ break;
2456
+ case "linux":
2457
+ if (gpu.type === "cuda") {
2458
+ backends.push("cuda");
2459
+ }
2460
+ break;
2461
+ }
2462
+ }
2463
+ return backends;
2464
+ }
2465
+ /**
2466
+ * Determines the recommended model size based on the system's CPU and GPU.
2467
+ * @param {SystemCPU} cpu - The system's CPU.
2468
+ * @param {SystemGPU | null} gpu - The system's GPU, if available.
2469
+ * @returns {"small" | "medium" | "large"} - The recommended model size ("small", "medium", or "large").
2470
+ */
2471
+ getRecommendedModelSize(cpu, gpu) {
2472
+ if (gpu?.isAppleSilicon) {
2473
+ return cpu.memory.total > 16 * 1024 * 1024 * 1024 ? "medium" : "small";
2474
+ }
2475
+ if (gpu?.type === "cuda") {
2476
+ const gpuMemGB = (gpu.memory || 0) / 1024;
2477
+ if (gpuMemGB >= 16) return "large";
2478
+ if (gpuMemGB >= 8) return "medium";
2479
+ }
2480
+ if (cpu.memory.total > 32 * 1024 * 1024 * 1024) return "medium";
2481
+ return "small";
2482
+ }
2483
+ /**
2484
+ * Returns the SystemCapabilities of the PlatformManager.
2485
+ *
2486
+ * @returns {SystemCapabilities} The SystemCapabilities of the PlatformManager.
2487
+ * @throws {Error} if PlatformManager is not initialized.
2488
+ */
2489
+ getCapabilities() {
2490
+ if (!this.capabilities) {
2491
+ throw new Error("PlatformManager not initialized");
2492
+ }
2493
+ return this.capabilities;
2494
+ }
2495
+ /**
2496
+ * Checks if the device's GPU is Apple Silicon.
2497
+ * @returns {boolean} True if the GPU is Apple Silicon, false otherwise.
2498
+ */
2499
+ isAppleSilicon() {
2500
+ return !!this.capabilities?.gpu?.isAppleSilicon;
2501
+ }
2502
+ /**
2503
+ * Checks if the current device has GPU support.
2504
+ * @returns {boolean} - Returns true if the device has GPU support, false otherwise.
2505
+ */
2506
+ hasGPUSupport() {
2507
+ return !!this.capabilities?.gpu;
2508
+ }
2509
+ /**
2510
+ * Checks if the system supports CUDA GPU for processing.
2511
+ *
2512
+ * @returns {boolean} True if the system supports CUDA, false otherwise.
2513
+ */
2514
+ supportsCUDA() {
2515
+ return this.capabilities?.gpu?.type === "cuda";
2516
+ }
2517
+ /**
2518
+ * Check if the device supports Metal API for rendering graphics.
2519
+ * @returns {boolean} True if the device supports Metal, false otherwise.
2520
+ */
2521
+ supportsMetal() {
2522
+ return this.capabilities?.gpu?.type === "metal";
2523
+ }
2524
+ /**
2525
+ * Check if the device supports DirectML for GPU acceleration.
2526
+ *
2527
+ * @returns {boolean} True if the device supports DirectML, false otherwise.
2528
+ */
2529
+ supportsDirectML() {
2530
+ return this.capabilities?.gpu?.type === "directml";
2531
+ }
2532
+ /**
2533
+ * Get the recommended backend for computation based on the available capabilities.
2534
+ * @returns {"cuda" | "metal" | "directml" | "cpu"} The recommended backend for computation.
2535
+ * @throws {Error} Throws an error if PlatformManager is not initialized.
2536
+ */
2537
+ getRecommendedBackend() {
2538
+ if (!this.capabilities) {
2539
+ throw new Error("PlatformManager not initialized");
2540
+ }
2541
+ const { gpu, supportedBackends } = this.capabilities;
2542
+ if (gpu?.type === "cuda") return "cuda";
2543
+ if (gpu?.type === "metal") return "metal";
2544
+ if (supportedBackends.includes("directml")) return "directml";
2545
+ return "cpu";
2546
+ }
2547
+ };
2548
+ var getPlatformManager = () => {
2549
+ return PlatformManager.getInstance();
2550
+ };
2551
+
2424
2552
  // src/utils/studiolmManager.ts
2425
- import { logger as logger5, ModelTypes as ModelTypes2 } from "@elizaos/core";
2553
+ import { ModelTypes as ModelTypes2, logger as logger5 } from "@elizaos/core";
2426
2554
  var StudioLMManager = class _StudioLMManager {
2427
2555
  static instance = null;
2428
2556
  serverUrl;
@@ -2432,6 +2560,11 @@ var StudioLMManager = class _StudioLMManager {
2432
2560
  small: process.env.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
2433
2561
  medium: process.env.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b"
2434
2562
  };
2563
+ /**
2564
+ * Private constructor for StudioLMManager.
2565
+ * Initializes with default serverUrl if not provided in environment variables.
2566
+ * Logs initialization information including serverUrl, configuredModels, and timestamp.
2567
+ */
2435
2568
  constructor() {
2436
2569
  this.serverUrl = process.env.STUDIOLM_SERVER_URL || "http://localhost:1234";
2437
2570
  logger5.info("StudioLMManager initialized with configuration:", {
@@ -2440,12 +2573,20 @@ var StudioLMManager = class _StudioLMManager {
2440
2573
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2441
2574
  });
2442
2575
  }
2576
+ /**
2577
+ * Returns an instance of StudioLMManager. If an instance already exists, it returns the existing instance.
2578
+ * @returns {StudioLMManager} The instance of StudioLMManager
2579
+ */
2443
2580
  static getInstance() {
2444
2581
  if (!_StudioLMManager.instance) {
2445
2582
  _StudioLMManager.instance = new _StudioLMManager();
2446
2583
  }
2447
2584
  return _StudioLMManager.instance;
2448
2585
  }
2586
+ /**
2587
+ * Check the status of the server by sending a request to the /v1/models endpoint.
2588
+ * @returns {Promise<boolean>} A Promise that resolves to true if the server responds with success status, false otherwise.
2589
+ */
2449
2590
  async checkServerStatus() {
2450
2591
  try {
2451
2592
  const response = await fetch2(`${this.serverUrl}/v1/models`);
@@ -2462,6 +2603,11 @@ var StudioLMManager = class _StudioLMManager {
2462
2603
  return false;
2463
2604
  }
2464
2605
  }
2606
+ /**
2607
+ * Fetches the available models from the server and stores them in the 'availableModels' property.
2608
+ *
2609
+ * @returns {Promise<void>} A Promise that resolves when the models are fetched successfully or rejects with an error.
2610
+ */
2465
2611
  async fetchAvailableModels() {
2466
2612
  try {
2467
2613
  const response = await fetch2(`${this.serverUrl}/v1/models`);
@@ -2484,6 +2630,11 @@ var StudioLMManager = class _StudioLMManager {
2484
2630
  throw error;
2485
2631
  }
2486
2632
  }
2633
+ /**
2634
+ * Asynchronously tests the specified model with a chat completion request.
2635
+ * @param {string} modelId - The ID of the model to test.
2636
+ * @returns {Promise<boolean>} - A promise that resolves to true if the model test was successful, false otherwise.
2637
+ */
2487
2638
  async testModel(modelId) {
2488
2639
  try {
2489
2640
  const testRequest = {
@@ -2529,6 +2680,11 @@ var StudioLMManager = class _StudioLMManager {
2529
2680
  return false;
2530
2681
  }
2531
2682
  }
2683
+ /**
2684
+ * Tests the configured text models to ensure they are working properly.
2685
+ * Logs the results of the test and any failed models.
2686
+ * @returns {Promise<void>} A promise that resolves when the test is complete.
2687
+ */
2532
2688
  async testTextModels() {
2533
2689
  logger5.info("Testing configured text models...");
2534
2690
  const results = await Promise.all([
@@ -2549,6 +2705,12 @@ var StudioLMManager = class _StudioLMManager {
2549
2705
  logger5.success("All configured models passed the test");
2550
2706
  }
2551
2707
  }
2708
+ /**
2709
+ * Initializes StudioLM by checking server status, fetching available models,
2710
+ * and testing text models.
2711
+ *
2712
+ * @returns {Promise<void>} A Promise that resolves when initialization is complete
2713
+ */
2552
2714
  async initialize() {
2553
2715
  try {
2554
2716
  if (this.initialized) {
@@ -2572,12 +2734,29 @@ var StudioLMManager = class _StudioLMManager {
2572
2734
  throw error;
2573
2735
  }
2574
2736
  }
2737
+ /**
2738
+ * Retrieves the available models in the studio.
2739
+ *
2740
+ * @returns {StudioLMModel[]} An array of StudioLMModel objects representing the available models.
2741
+ */
2575
2742
  getAvailableModels() {
2576
2743
  return this.availableModels;
2577
2744
  }
2745
+ /**
2746
+ * Check if the object is initialized.
2747
+ *
2748
+ * @returns {boolean} Returns true if the object is initialized, otherwise false.
2749
+ */
2578
2750
  isInitialized() {
2579
2751
  return this.initialized;
2580
2752
  }
2753
+ /**
2754
+ * Asynchronously generates text using StudioLM based on provided parameters.
2755
+ *
2756
+ * @param {GenerateTextParams} params - The parameters for generating text.
2757
+ * @param {boolean} [isInitialized=false] - Flag to indicate if the model is already initialized.
2758
+ * @returns {Promise<string>} The generated text as a Promise.
2759
+ */
2581
2760
  async generateText(params, isInitialized = false) {
2582
2761
  try {
2583
2762
  logger5.info("StudioLM generateText entry:", {
@@ -2605,56 +2784,245 @@ var StudioLMManager = class _StudioLMManager {
2605
2784
  messageCount: messages.length,
2606
2785
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2607
2786
  });
2608
- logger5.info("Incoming context structure:", {
2609
- contextLength: params.prompt.length,
2610
- hasAction: params.prompt.includes("action"),
2611
- runtime: !!params.runtime,
2612
- stopSequences: params.stopSequences
2787
+ logger5.info("Incoming context structure:", {
2788
+ contextLength: params.prompt.length,
2789
+ hasAction: params.prompt.includes("action"),
2790
+ runtime: !!params.runtime,
2791
+ stopSequences: params.stopSequences
2792
+ });
2793
+ const request = {
2794
+ model: params.modelType === ModelTypes2.TEXT_LARGE ? this.configuredModels.medium : this.configuredModels.small,
2795
+ messages,
2796
+ temperature: 0.7,
2797
+ max_tokens: 8192,
2798
+ stream: false
2799
+ };
2800
+ const response = await fetch2(`${this.serverUrl}/v1/chat/completions`, {
2801
+ method: "POST",
2802
+ headers: {
2803
+ "Content-Type": "application/json"
2804
+ },
2805
+ body: JSON.stringify(request)
2806
+ });
2807
+ if (!response.ok) {
2808
+ throw new Error(`StudioLM request failed: ${response.status}`);
2809
+ }
2810
+ const result = await response.json();
2811
+ if (!result.choices?.[0]?.message?.content) {
2812
+ throw new Error("No valid response content received from StudioLM");
2813
+ }
2814
+ let responseText = result.choices[0].message.content;
2815
+ logger5.info("Raw response structure:", {
2816
+ responseLength: responseText.length,
2817
+ hasAction: responseText.includes("action"),
2818
+ hasThinkTag: responseText.includes("<think>")
2819
+ });
2820
+ if (responseText.includes("<think>")) {
2821
+ logger5.info("Cleaning think tags from response");
2822
+ responseText = responseText.replace(/<think>[\s\S]*?<\/think>\n?/g, "");
2823
+ logger5.info("Think tags removed from response");
2824
+ }
2825
+ logger5.info("StudioLM request completed successfully:", {
2826
+ responseLength: responseText.length,
2827
+ hasThinkTags: responseText.includes("<think>"),
2828
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2829
+ });
2830
+ return responseText;
2831
+ } catch (error) {
2832
+ logger5.error("StudioLM text generation error:", {
2833
+ error: error instanceof Error ? error.message : String(error),
2834
+ stack: error instanceof Error ? error.stack : void 0,
2835
+ phase: "text generation",
2836
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2837
+ });
2838
+ throw error;
2839
+ }
2840
+ }
2841
+ };
2842
+
2843
+ // src/utils/tokenizerManager.ts
2844
+ import { logger as logger6 } from "@elizaos/core";
2845
+ import {
2846
+ AutoTokenizer
2847
+ } from "@huggingface/transformers";
2848
+ var TokenizerManager = class _TokenizerManager {
2849
+ static instance = null;
2850
+ tokenizers;
2851
+ cacheDir;
2852
+ modelsDir;
2853
+ /**
2854
+ * Constructor for creating a new instance of the class.
2855
+ *
2856
+ * @param {string} cacheDir - The directory for caching data.
2857
+ * @param {string} modelsDir - The directory for storing models.
2858
+ */
2859
+ constructor(cacheDir, modelsDir) {
2860
+ this.tokenizers = /* @__PURE__ */ new Map();
2861
+ this.cacheDir = cacheDir;
2862
+ this.modelsDir = modelsDir;
2863
+ }
2864
+ /**
2865
+ * Get the singleton instance of TokenizerManager class. If the instance does not exist, it will create a new one.
2866
+ *
2867
+ * @param {string} cacheDir - The directory to cache the tokenizer models.
2868
+ * @param {string} modelsDir - The directory where tokenizer models are stored.
2869
+ * @returns {TokenizerManager} The singleton instance of TokenizerManager.
2870
+ */
2871
+ static getInstance(cacheDir, modelsDir) {
2872
+ if (!_TokenizerManager.instance) {
2873
+ _TokenizerManager.instance = new _TokenizerManager(cacheDir, modelsDir);
2874
+ }
2875
+ return _TokenizerManager.instance;
2876
+ }
2877
+ /**
2878
+ * Asynchronously loads a tokenizer based on the provided ModelSpec configuration.
2879
+ *
2880
+ * @param {ModelSpec} modelConfig - The configuration object for the model to load the tokenizer for.
2881
+ * @returns {Promise<PreTrainedTokenizer>} - A promise that resolves to the loaded tokenizer.
2882
+ */
2883
+ async loadTokenizer(modelConfig) {
2884
+ try {
2885
+ const tokenizerKey = `${modelConfig.tokenizer.type}-${modelConfig.tokenizer.name}`;
2886
+ logger6.info("Loading tokenizer:", {
2887
+ key: tokenizerKey,
2888
+ name: modelConfig.tokenizer.name,
2889
+ type: modelConfig.tokenizer.type,
2890
+ modelsDir: this.modelsDir,
2891
+ cacheDir: this.cacheDir
2892
+ });
2893
+ if (this.tokenizers.has(tokenizerKey)) {
2894
+ logger6.info("Using cached tokenizer:", { key: tokenizerKey });
2895
+ const cachedTokenizer = this.tokenizers.get(tokenizerKey);
2896
+ if (!cachedTokenizer) {
2897
+ throw new Error(
2898
+ `Tokenizer ${tokenizerKey} exists in map but returned undefined`
2899
+ );
2900
+ }
2901
+ return cachedTokenizer;
2902
+ }
2903
+ const fs6 = await import("node:fs");
2904
+ if (!fs6.existsSync(this.modelsDir)) {
2905
+ logger6.warn(
2906
+ "Models directory does not exist, creating it:",
2907
+ this.modelsDir
2908
+ );
2909
+ fs6.mkdirSync(this.modelsDir, { recursive: true });
2910
+ }
2911
+ logger6.info(
2912
+ "Initializing new tokenizer from HuggingFace with models directory:",
2913
+ this.modelsDir
2914
+ );
2915
+ try {
2916
+ const tokenizer = await AutoTokenizer.from_pretrained(
2917
+ modelConfig.tokenizer.name,
2918
+ {
2919
+ cache_dir: this.modelsDir,
2920
+ local_files_only: false
2921
+ }
2922
+ );
2923
+ this.tokenizers.set(tokenizerKey, tokenizer);
2924
+ logger6.success("Tokenizer loaded successfully:", { key: tokenizerKey });
2925
+ return tokenizer;
2926
+ } catch (tokenizeError) {
2927
+ logger6.error("Failed to load tokenizer from HuggingFace:", {
2928
+ error: tokenizeError instanceof Error ? tokenizeError.message : String(tokenizeError),
2929
+ stack: tokenizeError instanceof Error ? tokenizeError.stack : void 0,
2930
+ tokenizer: modelConfig.tokenizer.name,
2931
+ modelsDir: this.modelsDir
2932
+ });
2933
+ logger6.info("Retrying tokenizer loading...");
2934
+ const tokenizer = await AutoTokenizer.from_pretrained(
2935
+ modelConfig.tokenizer.name,
2936
+ {
2937
+ cache_dir: this.modelsDir,
2938
+ local_files_only: false
2939
+ }
2940
+ );
2941
+ this.tokenizers.set(tokenizerKey, tokenizer);
2942
+ logger6.success("Tokenizer loaded successfully on retry:", {
2943
+ key: tokenizerKey
2944
+ });
2945
+ return tokenizer;
2946
+ }
2947
+ } catch (error) {
2948
+ logger6.error("Failed to load tokenizer:", {
2949
+ error: error instanceof Error ? error.message : String(error),
2950
+ stack: error instanceof Error ? error.stack : void 0,
2951
+ model: modelConfig.name,
2952
+ tokenizer: modelConfig.tokenizer.name,
2953
+ modelsDir: this.modelsDir
2954
+ });
2955
+ throw error;
2956
+ }
2957
+ }
2958
+ /**
2959
+ * Encodes the given text using the specified tokenizer model configuration.
2960
+ *
2961
+ * @param {string} text - The text to encode.
2962
+ * @param {ModelSpec} modelConfig - The configuration for the model tokenizer.
2963
+ * @returns {Promise<number[]>} - An array of integers representing the encoded text.
2964
+ * @throws {Error} - If the text encoding fails, an error is thrown.
2965
+ */
2966
+ async encode(text, modelConfig) {
2967
+ try {
2968
+ logger6.info("Encoding text with tokenizer:", {
2969
+ length: text.length,
2970
+ tokenizer: modelConfig.tokenizer.name
2971
+ });
2972
+ const tokenizer = await this.loadTokenizer(modelConfig);
2973
+ logger6.info("Tokenizer loaded, encoding text...");
2974
+ const encoded = await tokenizer.encode(text, {
2975
+ add_special_tokens: true,
2976
+ return_token_type_ids: false
2613
2977
  });
2614
- const request = {
2615
- model: params.modelType === ModelTypes2.TEXT_LARGE ? this.configuredModels.medium : this.configuredModels.small,
2616
- messages,
2617
- temperature: 0.7,
2618
- max_tokens: 8192,
2619
- stream: false
2620
- };
2621
- const response = await fetch2(`${this.serverUrl}/v1/chat/completions`, {
2622
- method: "POST",
2623
- headers: {
2624
- "Content-Type": "application/json"
2625
- },
2626
- body: JSON.stringify(request)
2978
+ logger6.info("Text encoded successfully:", {
2979
+ tokenCount: encoded.length,
2980
+ tokenizer: modelConfig.tokenizer.name
2627
2981
  });
2628
- if (!response.ok) {
2629
- throw new Error(`StudioLM request failed: ${response.status}`);
2630
- }
2631
- const result = await response.json();
2632
- if (!result.choices?.[0]?.message?.content) {
2633
- throw new Error("No valid response content received from StudioLM");
2634
- }
2635
- let responseText = result.choices[0].message.content;
2636
- logger5.info("Raw response structure:", {
2637
- responseLength: responseText.length,
2638
- hasAction: responseText.includes("action"),
2639
- hasThinkTag: responseText.includes("<think>")
2982
+ return encoded;
2983
+ } catch (error) {
2984
+ logger6.error("Text encoding failed:", {
2985
+ error: error instanceof Error ? error.message : String(error),
2986
+ stack: error instanceof Error ? error.stack : void 0,
2987
+ textLength: text.length,
2988
+ tokenizer: modelConfig.tokenizer.name,
2989
+ modelsDir: this.modelsDir
2640
2990
  });
2641
- if (responseText.includes("<think>")) {
2642
- logger5.info("Cleaning think tags from response");
2643
- responseText = responseText.replace(/<think>[\s\S]*?<\/think>\n?/g, "");
2644
- logger5.info("Think tags removed from response");
2645
- }
2646
- logger5.info("StudioLM request completed successfully:", {
2647
- responseLength: responseText.length,
2648
- hasThinkTags: responseText.includes("<think>"),
2649
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
2991
+ throw error;
2992
+ }
2993
+ }
2994
+ /**
2995
+ * Asynchronously decodes an array of tokens using a tokenizer based on the provided ModelSpec.
2996
+ *
2997
+ * @param {number[]} tokens - The array of tokens to be decoded.
2998
+ * @param {ModelSpec} modelConfig - The ModelSpec object containing information about the model and tokenizer to be used.
2999
+ * @returns {Promise<string>} - A Promise that resolves with the decoded text.
3000
+ * @throws {Error} - If an error occurs during token decoding.
3001
+ */
3002
+ async decode(tokens, modelConfig) {
3003
+ try {
3004
+ logger6.info("Decoding tokens with tokenizer:", {
3005
+ count: tokens.length,
3006
+ tokenizer: modelConfig.tokenizer.name
2650
3007
  });
2651
- return responseText;
3008
+ const tokenizer = await this.loadTokenizer(modelConfig);
3009
+ logger6.info("Tokenizer loaded, decoding tokens...");
3010
+ const decoded = await tokenizer.decode(tokens, {
3011
+ skip_special_tokens: true,
3012
+ clean_up_tokenization_spaces: true
3013
+ });
3014
+ logger6.info("Tokens decoded successfully:", {
3015
+ textLength: decoded.length,
3016
+ tokenizer: modelConfig.tokenizer.name
3017
+ });
3018
+ return decoded;
2652
3019
  } catch (error) {
2653
- logger5.error("StudioLM text generation error:", {
3020
+ logger6.error("Token decoding failed:", {
2654
3021
  error: error instanceof Error ? error.message : String(error),
2655
3022
  stack: error instanceof Error ? error.stack : void 0,
2656
- phase: "text generation",
2657
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3023
+ tokenCount: tokens.length,
3024
+ tokenizer: modelConfig.tokenizer.name,
3025
+ modelsDir: this.modelsDir
2658
3026
  });
2659
3027
  throw error;
2660
3028
  }
@@ -2662,12 +3030,12 @@ var StudioLMManager = class _StudioLMManager {
2662
3030
  };
2663
3031
 
2664
3032
  // src/utils/transcribeManager.ts
2665
- import { logger as logger6 } from "@elizaos/core";
2666
- import { nodewhisper } from "nodejs-whisper";
3033
+ import { exec as exec2 } from "node:child_process";
2667
3034
  import fs2 from "node:fs";
2668
3035
  import path2 from "node:path";
2669
3036
  import { promisify as promisify3 } from "node:util";
2670
- import { exec as exec2 } from "node:child_process";
3037
+ import { logger as logger7 } from "@elizaos/core";
3038
+ import { nodewhisper } from "nodejs-whisper";
2671
3039
  var execAsync2 = promisify3(exec2);
2672
3040
  var TranscribeManager = class _TranscribeManager {
2673
3041
  static instance = null;
@@ -2676,21 +3044,30 @@ var TranscribeManager = class _TranscribeManager {
2676
3044
  ffmpegVersion = null;
2677
3045
  ffmpegPath = null;
2678
3046
  ffmpegInitialized = false;
3047
+ /**
3048
+ * Constructor for TranscribeManager class.
3049
+ *
3050
+ * @param {string} cacheDir - The directory path for storing cached files.
3051
+ */
2679
3052
  constructor(cacheDir) {
2680
3053
  this.cacheDir = path2.join(cacheDir, "whisper");
2681
- logger6.info("Initializing TranscribeManager", {
3054
+ logger7.info("Initializing TranscribeManager", {
2682
3055
  cacheDir: this.cacheDir,
2683
3056
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2684
3057
  });
2685
3058
  this.ensureCacheDirectory();
2686
3059
  }
3060
+ /**
3061
+ * Ensures that FFmpeg is initialized and available for use.
3062
+ * @returns {Promise<boolean>} A promise that resolves to a boolean value indicating if FFmpeg is available.
3063
+ */
2687
3064
  async ensureFFmpeg() {
2688
3065
  if (!this.ffmpegInitialized) {
2689
3066
  try {
2690
3067
  await this.initializeFFmpeg();
2691
3068
  this.ffmpegInitialized = true;
2692
3069
  } catch (error) {
2693
- logger6.error("FFmpeg initialization failed:", {
3070
+ logger7.error("FFmpeg initialization failed:", {
2694
3071
  error: error instanceof Error ? error.message : String(error),
2695
3072
  stack: error instanceof Error ? error.stack : void 0,
2696
3073
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2700,31 +3077,57 @@ var TranscribeManager = class _TranscribeManager {
2700
3077
  }
2701
3078
  return this.ffmpegAvailable;
2702
3079
  }
3080
+ /**
3081
+ * Checks if FFmpeg is available.
3082
+ * @returns {boolean} True if FFmpeg is available, false otherwise.
3083
+ */
2703
3084
  isFFmpegAvailable() {
2704
3085
  return this.ffmpegAvailable;
2705
3086
  }
3087
+ /**
3088
+ * Asynchronously retrieves the FFmpeg version if it hasn't been fetched yet.
3089
+ * If the FFmpeg version has already been fetched, it will return the stored version.
3090
+ * @returns A Promise that resolves with the FFmpeg version as a string, or null if the version is not available.
3091
+ */
2706
3092
  async getFFmpegVersion() {
2707
3093
  if (!this.ffmpegVersion) {
2708
3094
  await this.fetchFFmpegVersion();
2709
3095
  }
2710
3096
  return this.ffmpegVersion;
2711
3097
  }
3098
+ /**
3099
+ * Fetches the FFmpeg version by executing the command "ffmpeg -version".
3100
+ * Updates the class property ffmpegVersion with the retrieved version.
3101
+ * Logs the FFmpeg version information or error message.
3102
+ * @returns {Promise<void>} A Promise that resolves once the FFmpeg version is fetched and logged.
3103
+ */
2712
3104
  async fetchFFmpegVersion() {
2713
3105
  try {
2714
3106
  const { stdout } = await execAsync2("ffmpeg -version");
2715
3107
  this.ffmpegVersion = stdout.split("\n")[0];
2716
- logger6.info("FFmpeg version:", {
3108
+ logger7.info("FFmpeg version:", {
2717
3109
  version: this.ffmpegVersion,
2718
3110
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2719
3111
  });
2720
3112
  } catch (error) {
2721
3113
  this.ffmpegVersion = null;
2722
- logger6.error("Failed to get FFmpeg version:", {
3114
+ logger7.error("Failed to get FFmpeg version:", {
2723
3115
  error: error instanceof Error ? error.message : String(error),
2724
3116
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2725
3117
  });
2726
3118
  }
2727
3119
  }
3120
+ /**
3121
+ * Initializes FFmpeg by performing the following steps:
3122
+ * 1. Checks for FFmpeg availability in PATH
3123
+ * 2. Retrieves FFmpeg version information
3124
+ * 3. Verifies FFmpeg capabilities
3125
+ *
3126
+ * If FFmpeg is available, logs a success message with version, path, and timestamp.
3127
+ * If FFmpeg is not available, logs installation instructions.
3128
+ *
3129
+ * @returns A Promise that resolves once FFmpeg has been successfully initialized
3130
+ */
2728
3131
  async initializeFFmpeg() {
2729
3132
  try {
2730
3133
  await this.checkFFmpegAvailability();
@@ -2736,7 +3139,7 @@ var TranscribeManager = class _TranscribeManager {
2736
3139
  }
2737
3140
  } catch (error) {
2738
3141
  this.ffmpegAvailable = false;
2739
- logger6.error("FFmpeg initialization failed:", {
3142
+ logger7.error("FFmpeg initialization failed:", {
2740
3143
  error: error instanceof Error ? error.message : String(error),
2741
3144
  stack: error instanceof Error ? error.stack : void 0,
2742
3145
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2744,6 +3147,13 @@ var TranscribeManager = class _TranscribeManager {
2744
3147
  this.logFFmpegInstallInstructions();
2745
3148
  }
2746
3149
  }
3150
+ /**
3151
+ * Asynchronously checks for the availability of FFmpeg in the system by executing a command to find the FFmpeg location.
3152
+ * Updates the class properties `ffmpegPath` and `ffmpegAvailable` accordingly.
3153
+ * Logs relevant information such as FFmpeg location and potential errors using the logger.
3154
+ *
3155
+ * @returns A Promise that resolves with no value upon completion.
3156
+ */
2747
3157
  async checkFFmpegAvailability() {
2748
3158
  try {
2749
3159
  const { stdout, stderr } = await execAsync2(
@@ -2751,7 +3161,7 @@ var TranscribeManager = class _TranscribeManager {
2751
3161
  );
2752
3162
  this.ffmpegPath = stdout.trim();
2753
3163
  this.ffmpegAvailable = true;
2754
- logger6.info("FFmpeg found at:", {
3164
+ logger7.info("FFmpeg found at:", {
2755
3165
  path: this.ffmpegPath,
2756
3166
  stderr: stderr ? stderr.trim() : void 0,
2757
3167
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -2759,13 +3169,18 @@ var TranscribeManager = class _TranscribeManager {
2759
3169
  } catch (error) {
2760
3170
  this.ffmpegAvailable = false;
2761
3171
  this.ffmpegPath = null;
2762
- logger6.error("FFmpeg not found in PATH:", {
3172
+ logger7.error("FFmpeg not found in PATH:", {
2763
3173
  error: error instanceof Error ? error.message : String(error),
2764
3174
  stderr: error instanceof Error && "stderr" in error ? error.stderr : void 0,
2765
3175
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2766
3176
  });
2767
3177
  }
2768
3178
  }
3179
+ /**
3180
+ * Verifies the FFmpeg capabilities by checking if FFmpeg supports the required codecs and formats.
3181
+ *
3182
+ * @returns {Promise<void>} A Promise that resolves if FFmpeg has the required codecs, otherwise rejects with an error message.
3183
+ */
2769
3184
  async verifyFFmpegCapabilities() {
2770
3185
  try {
2771
3186
  const { stdout } = await execAsync2("ffmpeg -codecs");
@@ -2776,15 +3191,18 @@ var TranscribeManager = class _TranscribeManager {
2776
3191
  );
2777
3192
  }
2778
3193
  } catch (error) {
2779
- logger6.error("FFmpeg capabilities verification failed:", {
3194
+ logger7.error("FFmpeg capabilities verification failed:", {
2780
3195
  error: error instanceof Error ? error.message : String(error),
2781
3196
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2782
3197
  });
2783
3198
  throw error;
2784
3199
  }
2785
3200
  }
3201
+ /**
3202
+ * Logs instructions on how to install FFmpeg if it is not properly installed.
3203
+ */
2786
3204
  logFFmpegInstallInstructions() {
2787
- logger6.warn(
3205
+ logger7.warn(
2788
3206
  "FFmpeg is required but not properly installed. Please install FFmpeg:",
2789
3207
  {
2790
3208
  instructions: {
@@ -2799,17 +3217,36 @@ var TranscribeManager = class _TranscribeManager {
2799
3217
  }
2800
3218
  );
2801
3219
  }
3220
+ /**
3221
+ * Gets the singleton instance of TranscribeManager, creates a new instance if it doesn't exist.
3222
+ *
3223
+ * @param {string} cacheDir - The directory path for caching transcriptions.
3224
+ * @returns {TranscribeManager} The singleton instance of TranscribeManager.
3225
+ */
2802
3226
  static getInstance(cacheDir) {
2803
3227
  if (!_TranscribeManager.instance) {
2804
3228
  _TranscribeManager.instance = new _TranscribeManager(cacheDir);
2805
3229
  }
2806
3230
  return _TranscribeManager.instance;
2807
3231
  }
3232
+ /**
3233
+ * Ensures that the cache directory exists. If it doesn't exist,
3234
+ * creates the directory using fs.mkdirSync with recursive set to true.
3235
+ * @returns {void}
3236
+ */
2808
3237
  ensureCacheDirectory() {
2809
3238
  if (!fs2.existsSync(this.cacheDir)) {
2810
3239
  fs2.mkdirSync(this.cacheDir, { recursive: true });
2811
3240
  }
2812
3241
  }
3242
+ /**
3243
+ * Converts an audio file to WAV format using FFmpeg.
3244
+ *
3245
+ * @param {string} inputPath - The input path of the audio file to convert.
3246
+ * @param {string} outputPath - The output path where the converted WAV file will be saved.
3247
+ * @returns {Promise<void>} A Promise that resolves when the conversion is completed.
3248
+ * @throws {Error} If FFmpeg is not installed or not properly configured, or if the audio conversion fails.
3249
+ */
2813
3250
  async convertToWav(inputPath, outputPath) {
2814
3251
  if (!this.ffmpegAvailable) {
2815
3252
  throw new Error(
@@ -2821,7 +3258,7 @@ var TranscribeManager = class _TranscribeManager {
2821
3258
  `ffmpeg -y -loglevel error -i "${inputPath}" -acodec pcm_s16le -ar 16000 -ac 1 "${outputPath}"`
2822
3259
  );
2823
3260
  if (stderr) {
2824
- logger6.warn("FFmpeg conversion error:", {
3261
+ logger7.warn("FFmpeg conversion error:", {
2825
3262
  stderr,
2826
3263
  inputPath,
2827
3264
  outputPath,
@@ -2832,7 +3269,7 @@ var TranscribeManager = class _TranscribeManager {
2832
3269
  throw new Error("WAV file was not created successfully");
2833
3270
  }
2834
3271
  } catch (error) {
2835
- logger6.error("Audio conversion failed:", {
3272
+ logger7.error("Audio conversion failed:", {
2836
3273
  error: error instanceof Error ? error.message : String(error),
2837
3274
  stack: error instanceof Error ? error.stack : void 0,
2838
3275
  command: `ffmpeg -y -loglevel error -i "${inputPath}" -acodec pcm_s16le -ar 16000 -ac 1 "${outputPath}"`,
@@ -2846,6 +3283,14 @@ var TranscribeManager = class _TranscribeManager {
2846
3283
  );
2847
3284
  }
2848
3285
  }
3286
+ /**
3287
+ * Asynchronously preprocesses the audio by converting the provided audio buffer into a WAV file.
3288
+ * If FFmpeg is not installed, an error is thrown.
3289
+ *
3290
+ * @param {Buffer} audioBuffer The audio buffer to preprocess
3291
+ * @returns {Promise<string>} The path to the preprocessed WAV file
3292
+ * @throws {Error} If FFmpeg is not installed or if audio preprocessing fails
3293
+ */
2849
3294
  async preprocessAudio(audioBuffer) {
2850
3295
  if (!this.ffmpegAvailable) {
2851
3296
  throw new Error(
@@ -2865,7 +3310,7 @@ var TranscribeManager = class _TranscribeManager {
2865
3310
  }
2866
3311
  return tempWavFile;
2867
3312
  } catch (error) {
2868
- logger6.error("Audio preprocessing failed:", {
3313
+ logger7.error("Audio preprocessing failed:", {
2869
3314
  error: error instanceof Error ? error.message : String(error),
2870
3315
  stack: error instanceof Error ? error.stack : void 0,
2871
3316
  ffmpegAvailable: this.ffmpegAvailable,
@@ -2876,6 +3321,13 @@ var TranscribeManager = class _TranscribeManager {
2876
3321
  );
2877
3322
  }
2878
3323
  }
3324
+ /**
3325
+ * Transcribes the audio buffer to text using whisper.
3326
+ *
3327
+ * @param {Buffer} audioBuffer The audio buffer to transcribe.
3328
+ * @returns {Promise<TranscriptionResult>} A promise that resolves with the transcription result.
3329
+ * @throws {Error} If FFmpeg is not installed or properly configured.
3330
+ */
2879
3331
  async transcribe(audioBuffer) {
2880
3332
  await this.ensureFFmpeg();
2881
3333
  if (!this.ffmpegAvailable) {
@@ -2885,7 +3337,7 @@ var TranscribeManager = class _TranscribeManager {
2885
3337
  }
2886
3338
  try {
2887
3339
  const wavFile = await this.preprocessAudio(audioBuffer);
2888
- logger6.info("Starting transcription with whisper...");
3340
+ logger7.info("Starting transcription with whisper...");
2889
3341
  const originalStdoutWrite = process.stdout.write;
2890
3342
  const originalStderrWrite = process.stderr.write;
2891
3343
  const noopWrite = () => true;
@@ -2908,19 +3360,19 @@ var TranscribeManager = class _TranscribeManager {
2908
3360
  }
2909
3361
  if (fs2.existsSync(wavFile)) {
2910
3362
  fs2.unlinkSync(wavFile);
2911
- logger6.info("Temporary WAV file cleaned up");
3363
+ logger7.info("Temporary WAV file cleaned up");
2912
3364
  }
2913
3365
  const cleanText = output.split("\n").map((line) => {
2914
3366
  const textMatch = line.match(/](.+)$/);
2915
3367
  return textMatch ? textMatch[1].trim() : line.trim();
2916
3368
  }).filter((line) => line).join(" ");
2917
- logger6.success("Transcription complete:", {
3369
+ logger7.success("Transcription complete:", {
2918
3370
  textLength: cleanText.length,
2919
3371
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2920
3372
  });
2921
3373
  return { text: cleanText };
2922
3374
  } catch (error) {
2923
- logger6.error("Transcription failed:", {
3375
+ logger7.error("Transcription failed:", {
2924
3376
  error: error instanceof Error ? error.message : String(error),
2925
3377
  stack: error instanceof Error ? error.stack : void 0,
2926
3378
  ffmpegAvailable: this.ffmpegAvailable
@@ -2931,13 +3383,13 @@ var TranscribeManager = class _TranscribeManager {
2931
3383
  };
2932
3384
 
2933
3385
  // src/utils/ttsManager.ts
2934
- import { logger as logger7 } from "@elizaos/core";
2935
3386
  import fs3 from "node:fs";
2936
3387
  import path3 from "node:path";
3388
+ import { Readable } from "node:stream";
3389
+ import { logger as logger8 } from "@elizaos/core";
2937
3390
  import {
2938
3391
  getLlama
2939
3392
  } from "node-llama-cpp";
2940
- import { Readable } from "node:stream";
2941
3393
 
2942
3394
  // src/utils/audioUtils.ts
2943
3395
  import { PassThrough as PassThrough3 } from "node:stream";
@@ -2990,6 +3442,11 @@ var TTSManager = class _TTSManager {
2990
3442
  initialized = false;
2991
3443
  downloadManager;
2992
3444
  modelsDir;
3445
+ /**
3446
+ * Creates a new instance of TTSManager with the provided cache directory.
3447
+ *
3448
+ * @param {string} cacheDir - The directory where cached data will be stored.
3449
+ */
2993
3450
  constructor(cacheDir) {
2994
3451
  this.cacheDir = path3.join(cacheDir, "tts");
2995
3452
  this.modelsDir = process.env.LLAMALOCAL_PATH?.trim() ? path3.resolve(process.env.LLAMALOCAL_PATH.trim()) : path3.join(process.cwd(), "models");
@@ -2998,26 +3455,44 @@ var TTSManager = class _TTSManager {
2998
3455
  this.modelsDir
2999
3456
  );
3000
3457
  this.ensureCacheDirectory();
3001
- logger7.info("TTSManager initialized");
3458
+ logger8.info("TTSManager initialized");
3002
3459
  }
3460
+ /**
3461
+ * Returns an instance of TTSManager, creating a new one if none exist.
3462
+ *
3463
+ * @param {string} cacheDir - The directory path to store cached audio files.
3464
+ * @returns {TTSManager} An instance of TTSManager.
3465
+ */
3003
3466
  static getInstance(cacheDir) {
3004
3467
  if (!_TTSManager.instance) {
3005
3468
  _TTSManager.instance = new _TTSManager(cacheDir);
3006
3469
  }
3007
3470
  return _TTSManager.instance;
3008
3471
  }
3472
+ /**
3473
+ * Ensures that the cache directory exists. If it does not exist, the directory will be created.
3474
+ */
3009
3475
  ensureCacheDirectory() {
3010
3476
  if (!fs3.existsSync(this.cacheDir)) {
3011
3477
  fs3.mkdirSync(this.cacheDir, { recursive: true });
3012
- logger7.info("Created TTS cache directory:", this.cacheDir);
3478
+ logger8.info("Created TTS cache directory:", this.cacheDir);
3013
3479
  }
3014
3480
  }
3481
+ /**
3482
+ * Asynchronously initializes the TTS module with GGUF backend.
3483
+ * If already initialized or missing necessary components (model and context), it returns early.
3484
+ * Handles model download using different URL patterns as fallback if model not found locally.
3485
+ * Initializes the TTS model, creates context, and sets the sequence for TTS generation.
3486
+ * Logs detailed steps and final output of initialization.
3487
+ *
3488
+ * @returns {Promise<void>} A promise that resolves once the TTS module is fully initialized.
3489
+ */
3015
3490
  async initialize() {
3016
3491
  try {
3017
3492
  if (this.initialized && this.model && this.ctx) {
3018
3493
  return;
3019
3494
  }
3020
- logger7.info("Initializing TTS with GGUF backend...");
3495
+ logger8.info("Initializing TTS with GGUF backend...");
3021
3496
  const modelSpec = MODEL_SPECS.tts.base;
3022
3497
  const modelPath = path3.join(this.modelsDir, modelSpec.name);
3023
3498
  if (!fs3.existsSync(modelPath)) {
@@ -3041,7 +3516,7 @@ var TTSManager = class _TTSManager {
3041
3516
  let lastError = null;
3042
3517
  for (const attempt of attempts) {
3043
3518
  try {
3044
- logger7.info("Attempting TTS model download:", {
3519
+ logger8.info("Attempting TTS model download:", {
3045
3520
  description: attempt.description,
3046
3521
  repo: attempt.spec.repo,
3047
3522
  name: attempt.spec.name,
@@ -3050,18 +3525,18 @@ var TTSManager = class _TTSManager {
3050
3525
  });
3051
3526
  const barLength = 30;
3052
3527
  const emptyBar = "\u25B1".repeat(barLength);
3053
- logger7.info(`Downloading TTS model: ${emptyBar} 0%`);
3528
+ logger8.info(`Downloading TTS model: ${emptyBar} 0%`);
3054
3529
  await this.downloadManager.downloadFromUrl(attempt.url, modelPath);
3055
3530
  const completedBar = "\u25B0".repeat(barLength);
3056
- logger7.info(`Downloading TTS model: ${completedBar} 100%`);
3057
- logger7.success(
3531
+ logger8.info(`Downloading TTS model: ${completedBar} 100%`);
3532
+ logger8.success(
3058
3533
  "TTS model download successful with:",
3059
3534
  attempt.description
3060
3535
  );
3061
3536
  break;
3062
3537
  } catch (error) {
3063
3538
  lastError = error;
3064
- logger7.warn("TTS model download attempt failed:", {
3539
+ logger8.warn("TTS model download attempt failed:", {
3065
3540
  description: attempt.description,
3066
3541
  error: error instanceof Error ? error.message : String(error),
3067
3542
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -3072,7 +3547,7 @@ var TTSManager = class _TTSManager {
3072
3547
  throw lastError || new Error("All download attempts failed");
3073
3548
  }
3074
3549
  }
3075
- logger7.info("Loading TTS model...");
3550
+ logger8.info("Loading TTS model...");
3076
3551
  const llama = await getLlama();
3077
3552
  this.model = await llama.loadModel({
3078
3553
  modelPath,
@@ -3083,14 +3558,14 @@ var TTSManager = class _TTSManager {
3083
3558
  contextSize: modelSpec.contextSize
3084
3559
  });
3085
3560
  this.sequence = this.ctx.getSequence();
3086
- logger7.success("TTS initialization complete", {
3561
+ logger8.success("TTS initialization complete", {
3087
3562
  modelPath,
3088
3563
  contextSize: modelSpec.contextSize,
3089
3564
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3090
3565
  });
3091
3566
  this.initialized = true;
3092
3567
  } catch (error) {
3093
- logger7.error("TTS initialization failed:", {
3568
+ logger8.error("TTS initialization failed:", {
3094
3569
  error: error instanceof Error ? error.message : String(error),
3095
3570
  model: MODEL_SPECS.tts.base.name,
3096
3571
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
@@ -3098,20 +3573,26 @@ var TTSManager = class _TTSManager {
3098
3573
  throw error;
3099
3574
  }
3100
3575
  }
3576
+ /**
3577
+ * Asynchronously generates speech from a given text using the initialized TTS model.
3578
+ * @param {string} text - The text to generate speech from.
3579
+ * @returns {Promise<Readable>} A promise that resolves to a Readable stream containing the generated audio data.
3580
+ * @throws {Error} If the TTS model is not initialized or if no audio tokens are generated.
3581
+ */
3101
3582
  async generateSpeech(text) {
3102
3583
  try {
3103
3584
  await this.initialize();
3104
3585
  if (!this.model || !this.ctx || !this.sequence) {
3105
3586
  throw new Error("TTS model not initialized");
3106
3587
  }
3107
- logger7.info("Starting speech generation for text:", { text });
3588
+ logger8.info("Starting speech generation for text:", { text });
3108
3589
  const prompt = `[SPEAKER=female_1][LANGUAGE=en]${text}`;
3109
- logger7.info("Formatted prompt:", { prompt });
3110
- logger7.info("Tokenizing input...");
3590
+ logger8.info("Formatted prompt:", { prompt });
3591
+ logger8.info("Tokenizing input...");
3111
3592
  const inputTokens = this.model.tokenize(prompt);
3112
- logger7.info("Input tokenized:", { tokenCount: inputTokens.length });
3593
+ logger8.info("Input tokenized:", { tokenCount: inputTokens.length });
3113
3594
  const maxTokens = inputTokens.length * 2;
3114
- logger7.info("Starting token generation with optimized limit:", {
3595
+ logger8.info("Starting token generation with optimized limit:", {
3115
3596
  maxTokens
3116
3597
  });
3117
3598
  const responseTokens = [];
@@ -3127,28 +3608,28 @@ var TTSManager = class _TTSManager {
3127
3608
  responseTokens.length / maxTokens * barLength
3128
3609
  );
3129
3610
  const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
3130
- logger7.info(
3611
+ logger8.info(
3131
3612
  `Token generation: ${progressBar} ${percent}% (${responseTokens.length}/${maxTokens})`
3132
3613
  );
3133
3614
  if (responseTokens.length >= maxTokens) {
3134
- logger7.info("Token generation complete");
3615
+ logger8.info("Token generation complete");
3135
3616
  break;
3136
3617
  }
3137
3618
  }
3138
3619
  } catch (error) {
3139
- logger7.error("Token generation error:", error);
3620
+ logger8.error("Token generation error:", error);
3140
3621
  throw error;
3141
3622
  }
3142
3623
  if (responseTokens.length === 0) {
3143
3624
  throw new Error("No audio tokens generated");
3144
3625
  }
3145
- logger7.info("Converting tokens to audio data...");
3626
+ logger8.info("Converting tokens to audio data...");
3146
3627
  const audioData = this.processAudioResponse({
3147
3628
  tokens: responseTokens.map(
3148
3629
  (t) => Number.parseInt(this.model.detokenize([t]), 10)
3149
3630
  )
3150
3631
  });
3151
- logger7.info("Audio data generated:", {
3632
+ logger8.info("Audio data generated:", {
3152
3633
  byteLength: audioData.length,
3153
3634
  sampleRate: MODEL_SPECS.tts.base.sampleRate
3154
3635
  });
@@ -3159,16 +3640,27 @@ var TTSManager = class _TTSManager {
3159
3640
  1,
3160
3641
  16
3161
3642
  );
3162
- logger7.success("Speech generation complete");
3643
+ logger8.success("Speech generation complete");
3163
3644
  return audioStream;
3164
3645
  } catch (error) {
3165
- logger7.error("Speech generation failed:", {
3646
+ logger8.error("Speech generation failed:", {
3166
3647
  error: error instanceof Error ? error.message : String(error),
3167
3648
  text
3168
3649
  });
3169
3650
  throw error;
3170
3651
  }
3171
3652
  }
3653
+ /**
3654
+ * Processes the audio response from the TTS service by converting
3655
+ * the data to 16-bit PCM format.
3656
+ * If the response contains direct audio data, it converts Float32Array
3657
+ * to 16-bit PCM. If the response only contains tokens, it converts
3658
+ * them to PCM data. The actual conversion process may vary depending
3659
+ * on the model used.
3660
+ *
3661
+ * @param {TTSResponse} response - The response object from the TTS service
3662
+ * @returns {Buffer} The processed audio data in 16-bit PCM format
3663
+ */
3172
3664
  processAudioResponse(response) {
3173
3665
  if (response.audio) {
3174
3666
  const pcmData2 = new Int16Array(response.audio.length);
@@ -3188,19 +3680,19 @@ var TTSManager = class _TTSManager {
3188
3680
  };
3189
3681
 
3190
3682
  // src/utils/visionManager.ts
3191
- import { logger as logger8 } from "@elizaos/core";
3683
+ import { existsSync } from "node:fs";
3684
+ import fs4 from "node:fs";
3685
+ import os2 from "node:os";
3686
+ import path4 from "node:path";
3687
+ import process2 from "node:process";
3688
+ import { logger as logger9 } from "@elizaos/core";
3192
3689
  import {
3193
3690
  AutoProcessor,
3194
3691
  AutoTokenizer as AutoTokenizer2,
3195
- env,
3196
3692
  Florence2ForConditionalGeneration,
3197
- RawImage
3693
+ RawImage,
3694
+ env
3198
3695
  } from "@huggingface/transformers";
3199
- import { existsSync } from "node:fs";
3200
- import path4 from "node:path";
3201
- import process2 from "node:process";
3202
- import fs4 from "node:fs";
3203
- import os2 from "node:os";
3204
3696
  var VisionManager = class _VisionManager {
3205
3697
  static instance = null;
3206
3698
  model = null;
@@ -3220,6 +3712,11 @@ var VisionManager = class _VisionManager {
3220
3712
  { name: "decoder_model_merged", type: "decoder" },
3221
3713
  { name: "encoder_model", type: "encoder" }
3222
3714
  ];
3715
+ /**
3716
+ * Constructor for VisionManager class.
3717
+ *
3718
+ * @param {string} cacheDir - The directory path for caching vision models.
3719
+ */
3223
3720
  constructor(cacheDir) {
3224
3721
  this.modelsDir = path4.join(path4.dirname(cacheDir), "models", "vision");
3225
3722
  this.cacheDir = cacheDir;
@@ -3229,8 +3726,12 @@ var VisionManager = class _VisionManager {
3229
3726
  this.modelsDir
3230
3727
  );
3231
3728
  this.platformConfig = this.getPlatformConfig();
3232
- logger8.info("VisionManager initialized");
3729
+ logger9.info("VisionManager initialized");
3233
3730
  }
3731
+ /**
3732
+ * Retrieves the platform configuration based on the operating system and architecture.
3733
+ * @returns {PlatformConfig} The platform configuration object with device, dtype, and useOnnx properties.
3734
+ */
3234
3735
  getPlatformConfig() {
3235
3736
  const platform = os2.platform();
3236
3737
  const arch = os2.arch();
@@ -3255,25 +3756,42 @@ var VisionManager = class _VisionManager {
3255
3756
  };
3256
3757
  }
3257
3758
  }
3258
- logger8.info("Platform configuration detected:", {
3759
+ logger9.info("Platform configuration detected:", {
3259
3760
  platform,
3260
3761
  arch,
3261
3762
  config
3262
3763
  });
3263
3764
  return config;
3264
3765
  }
3766
+ /**
3767
+ * Ensures that the models directory exists. If it does not exist, it creates the directory.
3768
+ */
3265
3769
  ensureModelsDirExists() {
3266
3770
  if (!existsSync(this.modelsDir)) {
3267
- logger8.info(`Creating models directory at: ${this.modelsDir}`);
3771
+ logger9.info(`Creating models directory at: ${this.modelsDir}`);
3268
3772
  fs4.mkdirSync(this.modelsDir, { recursive: true });
3269
3773
  }
3270
3774
  }
3775
+ /**
3776
+ * Returns the singleton instance of VisionManager.
3777
+ * If an instance does not already exist, a new instance is created with the specified cache directory.
3778
+ *
3779
+ * @param {string} cacheDir - The directory where cache files will be stored.
3780
+ *
3781
+ * @returns {VisionManager} The singleton instance of VisionManager.
3782
+ */
3271
3783
  static getInstance(cacheDir) {
3272
3784
  if (!_VisionManager.instance) {
3273
3785
  _VisionManager.instance = new _VisionManager(cacheDir);
3274
3786
  }
3275
3787
  return _VisionManager.instance;
3276
3788
  }
3789
+ /**
3790
+ * Check if the cache exists for the specified model or tokenizer or processor.
3791
+ * @param {string} modelId - The ID of the model.
3792
+ * @param {"model" | "tokenizer" | "processor"} type - The type of the cache ("model", "tokenizer", or "processor").
3793
+ * @returns {boolean} - Returns true if cache exists, otherwise returns false.
3794
+ */
3277
3795
  checkCacheExists(modelId, type) {
3278
3796
  const modelPath = path4.join(
3279
3797
  this.modelsDir,
@@ -3281,11 +3799,16 @@ var VisionManager = class _VisionManager {
3281
3799
  type
3282
3800
  );
3283
3801
  if (existsSync(modelPath)) {
3284
- logger8.info(`${type} found at: ${modelPath}`);
3802
+ logger9.info(`${type} found at: ${modelPath}`);
3285
3803
  return true;
3286
3804
  }
3287
3805
  return false;
3288
3806
  }
3807
+ /**
3808
+ * Configures the model components based on the platform and architecture.
3809
+ * Sets the default data type (dtype) for components based on platform capabilities.
3810
+ * Updates all component dtypes to match the default dtype.
3811
+ */
3289
3812
  configureModelComponents() {
3290
3813
  const platform = os2.platform();
3291
3814
  const arch = os2.arch();
@@ -3299,13 +3822,18 @@ var VisionManager = class _VisionManager {
3299
3822
  ...component,
3300
3823
  dtype: defaultDtype
3301
3824
  }));
3302
- logger8.info("Model components configured with dtype:", {
3825
+ logger9.info("Model components configured with dtype:", {
3303
3826
  platform,
3304
3827
  arch,
3305
3828
  defaultDtype,
3306
3829
  components: this.modelComponents.map((c) => `${c.name}: ${c.dtype}`)
3307
3830
  });
3308
3831
  }
3832
+ /**
3833
+ * Get the model configuration based on the input component name.
3834
+ * @param {string} componentName - The name of the component to retrieve the configuration for.
3835
+ * @returns {object} The model configuration object containing device, dtype, and cache_dir.
3836
+ */
3309
3837
  getModelConfig(componentName) {
3310
3838
  const component = this.modelComponents.find(
3311
3839
  (c) => c.name === componentName
@@ -3316,24 +3844,30 @@ var VisionManager = class _VisionManager {
3316
3844
  cache_dir: this.modelsDir
3317
3845
  };
3318
3846
  }
3847
+ /**
3848
+ * Asynchronous method to initialize the vision model by loading Florence2 model, vision tokenizer, and vision processor.
3849
+ *
3850
+ * @returns {Promise<void>} - Promise that resolves once the initialization process is completed.
3851
+ * @throws {Error} - If there is an error during the initialization process.
3852
+ */
3319
3853
  async initialize() {
3320
3854
  try {
3321
3855
  if (this.initialized) {
3322
- logger8.info(
3856
+ logger9.info(
3323
3857
  "Vision model already initialized, skipping initialization"
3324
3858
  );
3325
3859
  return;
3326
3860
  }
3327
- logger8.info("Starting vision model initialization...");
3861
+ logger9.info("Starting vision model initialization...");
3328
3862
  const modelSpec = MODEL_SPECS.vision;
3329
- logger8.info("Configuring environment for vision model...");
3863
+ logger9.info("Configuring environment for vision model...");
3330
3864
  env.allowLocalModels = true;
3331
3865
  env.allowRemoteModels = true;
3332
3866
  if (this.platformConfig.useOnnx) {
3333
3867
  env.backends.onnx.enabled = true;
3334
3868
  env.backends.onnx.logLevel = "info";
3335
3869
  }
3336
- logger8.info("Loading Florence2 model...");
3870
+ logger9.info("Loading Florence2 model...");
3337
3871
  try {
3338
3872
  let lastProgress = -1;
3339
3873
  const modelCached = this.checkCacheExists(modelSpec.modelId, "model");
@@ -3355,7 +3889,7 @@ var VisionManager = class _VisionManager {
3355
3889
  currentProgress / 100 * barLength
3356
3890
  );
3357
3891
  const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
3358
- logger8.info(
3892
+ logger9.info(
3359
3893
  `Downloading vision model: ${progressBar} ${currentProgress}%`
3360
3894
  );
3361
3895
  if (currentProgress === 100) this.modelDownloaded = true;
@@ -3364,16 +3898,16 @@ var VisionManager = class _VisionManager {
3364
3898
  }
3365
3899
  );
3366
3900
  this.model = model;
3367
- logger8.success("Florence2 model loaded successfully");
3901
+ logger9.success("Florence2 model loaded successfully");
3368
3902
  } catch (error) {
3369
- logger8.error("Failed to load Florence2 model:", {
3903
+ logger9.error("Failed to load Florence2 model:", {
3370
3904
  error: error instanceof Error ? error.message : String(error),
3371
3905
  stack: error instanceof Error ? error.stack : void 0,
3372
3906
  modelId: modelSpec.modelId
3373
3907
  });
3374
3908
  throw error;
3375
3909
  }
3376
- logger8.info("Loading vision tokenizer...");
3910
+ logger9.info("Loading vision tokenizer...");
3377
3911
  try {
3378
3912
  const tokenizerCached = this.checkCacheExists(
3379
3913
  modelSpec.modelId,
@@ -3396,7 +3930,7 @@ var VisionManager = class _VisionManager {
3396
3930
  currentProgress / 100 * barLength
3397
3931
  );
3398
3932
  const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
3399
- logger8.info(
3933
+ logger9.info(
3400
3934
  `Downloading vision tokenizer: ${progressBar} ${currentProgress}%`
3401
3935
  );
3402
3936
  if (currentProgress === 100) this.tokenizerDownloaded = true;
@@ -3404,16 +3938,16 @@ var VisionManager = class _VisionManager {
3404
3938
  }
3405
3939
  }
3406
3940
  );
3407
- logger8.success("Vision tokenizer loaded successfully");
3941
+ logger9.success("Vision tokenizer loaded successfully");
3408
3942
  } catch (error) {
3409
- logger8.error("Failed to load tokenizer:", {
3943
+ logger9.error("Failed to load tokenizer:", {
3410
3944
  error: error instanceof Error ? error.message : String(error),
3411
3945
  stack: error instanceof Error ? error.stack : void 0,
3412
3946
  modelId: modelSpec.modelId
3413
3947
  });
3414
3948
  throw error;
3415
3949
  }
3416
- logger8.info("Loading vision processor...");
3950
+ logger9.info("Loading vision processor...");
3417
3951
  try {
3418
3952
  const processorCached = this.checkCacheExists(
3419
3953
  modelSpec.modelId,
@@ -3437,7 +3971,7 @@ var VisionManager = class _VisionManager {
3437
3971
  currentProgress / 100 * barLength
3438
3972
  );
3439
3973
  const progressBar = "\u25B0".repeat(filledLength) + "\u25B1".repeat(barLength - filledLength);
3440
- logger8.info(
3974
+ logger9.info(
3441
3975
  `Downloading vision processor: ${progressBar} ${currentProgress}%`
3442
3976
  );
3443
3977
  if (currentProgress === 100) this.processorDownloaded = true;
@@ -3445,9 +3979,9 @@ var VisionManager = class _VisionManager {
3445
3979
  }
3446
3980
  }
3447
3981
  );
3448
- logger8.success("Vision processor loaded successfully");
3982
+ logger9.success("Vision processor loaded successfully");
3449
3983
  } catch (error) {
3450
- logger8.error("Failed to load vision processor:", {
3984
+ logger9.error("Failed to load vision processor:", {
3451
3985
  error: error instanceof Error ? error.message : String(error),
3452
3986
  stack: error instanceof Error ? error.stack : void 0,
3453
3987
  modelId: modelSpec.modelId
@@ -3455,9 +3989,9 @@ var VisionManager = class _VisionManager {
3455
3989
  throw error;
3456
3990
  }
3457
3991
  this.initialized = true;
3458
- logger8.success("Vision model initialization complete");
3992
+ logger9.success("Vision model initialization complete");
3459
3993
  } catch (error) {
3460
- logger8.error("Vision model initialization failed:", {
3994
+ logger9.error("Vision model initialization failed:", {
3461
3995
  error: error instanceof Error ? error.message : String(error),
3462
3996
  stack: error instanceof Error ? error.stack : void 0,
3463
3997
  modelsDir: this.modelsDir
@@ -3465,15 +3999,21 @@ var VisionManager = class _VisionManager {
3465
3999
  throw error;
3466
4000
  }
3467
4001
  }
4002
+ /**
4003
+ * Fetches an image from a given URL and returns the image data as a Buffer along with its MIME type.
4004
+ *
4005
+ * @param {string} url - The URL of the image to fetch.
4006
+ * @returns {Promise<{ buffer: Buffer; mimeType: string }>} Object containing the image data as a Buffer and its MIME type.
4007
+ */
3468
4008
  async fetchImage(url) {
3469
4009
  try {
3470
- logger8.info(`Fetching image from URL: ${url.slice(0, 100)}...`);
4010
+ logger9.info(`Fetching image from URL: ${url.slice(0, 100)}...`);
3471
4011
  if (url.startsWith("data:")) {
3472
- logger8.info("Processing data URL...");
4012
+ logger9.info("Processing data URL...");
3473
4013
  const [header, base64Data] = url.split(",");
3474
4014
  const mimeType2 = header.split(";")[0].split(":")[1];
3475
4015
  const buffer2 = Buffer.from(base64Data, "base64");
3476
- logger8.info("Data URL processed successfully");
4016
+ logger9.info("Data URL processed successfully");
3477
4017
  return { buffer: buffer2, mimeType: mimeType2 };
3478
4018
  }
3479
4019
  const response = await fetch(url);
@@ -3482,14 +4022,14 @@ var VisionManager = class _VisionManager {
3482
4022
  }
3483
4023
  const buffer = Buffer.from(await response.arrayBuffer());
3484
4024
  const mimeType = response.headers.get("content-type") || "image/jpeg";
3485
- logger8.info("Image fetched successfully:", {
4025
+ logger9.info("Image fetched successfully:", {
3486
4026
  mimeType,
3487
4027
  bufferSize: buffer.length,
3488
4028
  status: response.status
3489
4029
  });
3490
4030
  return { buffer, mimeType };
3491
4031
  } catch (error) {
3492
- logger8.error("Failed to fetch image:", {
4032
+ logger9.error("Failed to fetch image:", {
3493
4033
  error: error instanceof Error ? error.message : String(error),
3494
4034
  stack: error instanceof Error ? error.stack : void 0,
3495
4035
  url
@@ -3497,39 +4037,44 @@ var VisionManager = class _VisionManager {
3497
4037
  throw error;
3498
4038
  }
3499
4039
  }
4040
+ /**
4041
+ * Processes the image from the provided URL using the initialized vision model components.
4042
+ * @param {string} imageUrl - The URL of the image to process.
4043
+ * @returns {Promise<{ title: string; description: string }>} An object containing the title and description of the processed image.
4044
+ */
3500
4045
  async processImage(imageUrl) {
3501
4046
  try {
3502
- logger8.info("Starting image processing...");
4047
+ logger9.info("Starting image processing...");
3503
4048
  if (!this.initialized) {
3504
- logger8.info("Vision model not initialized, initializing now...");
4049
+ logger9.info("Vision model not initialized, initializing now...");
3505
4050
  await this.initialize();
3506
4051
  }
3507
4052
  if (!this.model || !this.processor || !this.tokenizer) {
3508
4053
  throw new Error("Vision model components not properly initialized");
3509
4054
  }
3510
- logger8.info("Fetching image...");
4055
+ logger9.info("Fetching image...");
3511
4056
  const { buffer, mimeType } = await this.fetchImage(imageUrl);
3512
- logger8.info("Creating image blob...");
4057
+ logger9.info("Creating image blob...");
3513
4058
  const blob = new Blob([buffer], { type: mimeType });
3514
- logger8.info("Converting blob to RawImage...");
4059
+ logger9.info("Converting blob to RawImage...");
3515
4060
  const image = await RawImage.fromBlob(blob);
3516
- logger8.info("Processing image with vision processor...");
4061
+ logger9.info("Processing image with vision processor...");
3517
4062
  const visionInputs = await this.processor(image);
3518
- logger8.info("Constructing prompts...");
4063
+ logger9.info("Constructing prompts...");
3519
4064
  const prompts = this.processor.construct_prompts("<DETAILED_CAPTION>");
3520
- logger8.info("Tokenizing prompts...");
4065
+ logger9.info("Tokenizing prompts...");
3521
4066
  const textInputs = this.tokenizer(prompts);
3522
- logger8.info("Generating image description...");
4067
+ logger9.info("Generating image description...");
3523
4068
  const generatedIds = await this.model.generate({
3524
4069
  ...textInputs,
3525
4070
  ...visionInputs,
3526
4071
  max_new_tokens: MODEL_SPECS.vision.maxTokens
3527
4072
  });
3528
- logger8.info("Decoding generated text...");
4073
+ logger9.info("Decoding generated text...");
3529
4074
  const generatedText = this.tokenizer.batch_decode(generatedIds, {
3530
4075
  skip_special_tokens: false
3531
4076
  })[0];
3532
- logger8.info("Post-processing generation...");
4077
+ logger9.info("Post-processing generation...");
3533
4078
  const result = this.processor.post_process_generation(
3534
4079
  generatedText,
3535
4080
  "<DETAILED_CAPTION>",
@@ -3540,103 +4085,25 @@ var VisionManager = class _VisionManager {
3540
4085
  title: `${detailedCaption.split(".")[0]}.`,
3541
4086
  description: detailedCaption
3542
4087
  };
3543
- logger8.success("Image processing complete:", {
4088
+ logger9.success("Image processing complete:", {
3544
4089
  titleLength: response.title.length,
3545
4090
  descriptionLength: response.description.length
3546
4091
  });
3547
4092
  return response;
3548
4093
  } catch (error) {
3549
- logger8.error("Image processing failed:", {
4094
+ logger9.error("Image processing failed:", {
3550
4095
  error: error instanceof Error ? error.message : String(error),
3551
4096
  stack: error instanceof Error ? error.stack : void 0,
3552
4097
  imageUrl,
3553
4098
  modelInitialized: this.initialized,
3554
4099
  hasModel: !!this.model,
3555
- hasProcessor: !!this.processor,
3556
- hasTokenizer: !!this.tokenizer
3557
- });
3558
- throw error;
3559
- }
3560
- }
3561
- };
3562
-
3563
- // src/environment.ts
3564
- import { z } from "zod";
3565
- import { logger as logger9 } from "@elizaos/core";
3566
- var configSchema = z.object({
3567
- USE_LOCAL_AI: z.boolean().default(true),
3568
- USE_STUDIOLM_TEXT_MODELS: z.boolean().default(false),
3569
- USE_OLLAMA_TEXT_MODELS: z.boolean().default(false),
3570
- // Ollama Configuration
3571
- OLLAMA_SERVER_URL: z.string().default("http://localhost:11434"),
3572
- OLLAMA_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
3573
- USE_OLLAMA_EMBEDDING: z.boolean().default(false),
3574
- OLLAMA_EMBEDDING_MODEL: z.string().default(""),
3575
- SMALL_OLLAMA_MODEL: z.string().default("deepseek-r1:1.5b"),
3576
- MEDIUM_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
3577
- LARGE_OLLAMA_MODEL: z.string().default("deepseek-r1:7b"),
3578
- // StudioLM Configuration
3579
- STUDIOLM_SERVER_URL: z.string().default("http://localhost:1234"),
3580
- STUDIOLM_SMALL_MODEL: z.string().default("lmstudio-community/deepseek-r1-distill-qwen-1.5b"),
3581
- STUDIOLM_MEDIUM_MODEL: z.string().default("deepseek-r1-distill-qwen-7b"),
3582
- STUDIOLM_EMBEDDING_MODEL: z.union([z.boolean(), z.string()]).default(false)
3583
- });
3584
- function validateModelConfig(config) {
3585
- logger9.info("Validating model configuration with values:", {
3586
- USE_LOCAL_AI: config.USE_LOCAL_AI,
3587
- USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS,
3588
- USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS
3589
- });
3590
- if (!config.USE_LOCAL_AI) {
3591
- config.USE_LOCAL_AI = true;
3592
- logger9.info("Setting USE_LOCAL_AI to true as it's required");
3593
- }
3594
- if (config.USE_STUDIOLM_TEXT_MODELS && config.USE_OLLAMA_TEXT_MODELS) {
3595
- throw new Error(
3596
- "StudioLM and Ollama text models cannot be enabled simultaneously"
3597
- );
3598
- }
3599
- logger9.info("Configuration is valid");
3600
- }
3601
- async function validateConfig(config) {
3602
- try {
3603
- const booleanConfig = {
3604
- USE_LOCAL_AI: true,
3605
- // Always true
3606
- USE_STUDIOLM_TEXT_MODELS: config.USE_STUDIOLM_TEXT_MODELS === "true",
3607
- USE_OLLAMA_TEXT_MODELS: config.USE_OLLAMA_TEXT_MODELS === "true",
3608
- USE_OLLAMA_EMBEDDING: config.USE_OLLAMA_EMBEDDING === "true"
3609
- };
3610
- validateModelConfig(booleanConfig);
3611
- const fullConfig = {
3612
- ...booleanConfig,
3613
- OLLAMA_SERVER_URL: config.OLLAMA_SERVER_URL || "http://localhost:11434",
3614
- OLLAMA_MODEL: config.OLLAMA_MODEL || "deepseek-r1-distill-qwen-7b",
3615
- OLLAMA_EMBEDDING_MODEL: config.OLLAMA_EMBEDDING_MODEL || "",
3616
- SMALL_OLLAMA_MODEL: config.SMALL_OLLAMA_MODEL || "deepseek-r1:1.5b",
3617
- MEDIUM_OLLAMA_MODEL: config.MEDIUM_OLLAMA_MODEL || "deepseek-r1:7b",
3618
- LARGE_OLLAMA_MODEL: config.LARGE_OLLAMA_MODEL || "deepseek-r1:7b",
3619
- STUDIOLM_SERVER_URL: config.STUDIOLM_SERVER_URL || "http://localhost:1234",
3620
- STUDIOLM_SMALL_MODEL: config.STUDIOLM_SMALL_MODEL || "lmstudio-community/deepseek-r1-distill-qwen-1.5b",
3621
- STUDIOLM_MEDIUM_MODEL: config.STUDIOLM_MEDIUM_MODEL || "deepseek-r1-distill-qwen-7b",
3622
- STUDIOLM_EMBEDDING_MODEL: config.STUDIOLM_EMBEDDING_MODEL || false
3623
- };
3624
- const validatedConfig = configSchema.parse(fullConfig);
3625
- return validatedConfig;
3626
- } catch (error) {
3627
- if (error instanceof z.ZodError) {
3628
- const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n");
3629
- logger9.error("Zod validation failed:", errorMessages);
3630
- throw new Error(`Configuration validation failed:
3631
- ${errorMessages}`);
4100
+ hasProcessor: !!this.processor,
4101
+ hasTokenizer: !!this.tokenizer
4102
+ });
4103
+ throw error;
3632
4104
  }
3633
- logger9.error("Configuration validation failed:", {
3634
- error: error instanceof Error ? error.message : String(error),
3635
- stack: error instanceof Error ? error.stack : void 0
3636
- });
3637
- throw error;
3638
4105
  }
3639
- }
4106
+ };
3640
4107
 
3641
4108
  // src/index.ts
3642
4109
  var __filename = fileURLToPath(import.meta.url);
@@ -3711,141 +4178,87 @@ var LocalAIManager = class _LocalAIManager {
3711
4178
  ttsManager;
3712
4179
  studioLMManager;
3713
4180
  ollamaManager;
4181
+ // Initialization state flags
3714
4182
  ollamaInitialized = false;
3715
4183
  studioLMInitialized = false;
4184
+ smallModelInitialized = false;
4185
+ mediumModelInitialized = false;
4186
+ embeddingInitialized = false;
4187
+ visionInitialized = false;
4188
+ transcriptionInitialized = false;
4189
+ ttsInitialized = false;
4190
+ // Initialization promises to prevent duplicate initialization
4191
+ smallModelInitializingPromise = null;
4192
+ mediumModelInitializingPromise = null;
4193
+ embeddingInitializingPromise = null;
4194
+ visionInitializingPromise = null;
4195
+ transcriptionInitializingPromise = null;
4196
+ ttsInitializingPromise = null;
4197
+ ollamaInitializingPromise = null;
4198
+ studioLMInitializingPromise = null;
3716
4199
  modelsDir;
4200
+ /**
4201
+ * Private constructor function to initialize base managers and paths.
4202
+ * This now only sets up the basic infrastructure without loading any models.
4203
+ */
3717
4204
  constructor() {
3718
4205
  const modelsDir = path5.join(process.cwd(), "models");
3719
- this.activeModelConfig = MODEL_SPECS.small;
3720
- this.modelPath = path5.join(modelsDir, MODEL_SPECS.small.name);
3721
- this.mediumModelPath = path5.join(modelsDir, MODEL_SPECS.medium.name);
3722
- this.cacheDir = path5.join(process.cwd(), "./cache");
3723
- this.modelsDir = modelsDir;
3724
- this.downloadManager = DownloadManager.getInstance(
3725
- this.cacheDir,
3726
- this.modelsDir
4206
+ if (process.env.LLAMALOCAL_PATH?.trim()) {
4207
+ this.modelsDir = path5.resolve(process.env.LLAMALOCAL_PATH.trim());
4208
+ } else {
4209
+ if (!fs5.existsSync(modelsDir)) {
4210
+ fs5.mkdirSync(modelsDir, { recursive: true });
4211
+ logger10.info("Created models directory");
4212
+ }
4213
+ this.modelsDir = modelsDir;
4214
+ }
4215
+ this.modelPath = path5.join(
4216
+ this.modelsDir,
4217
+ "DeepSeek-R1-Distill-Qwen-1.5B-Q8_0.gguf"
3727
4218
  );
3728
- this.tokenizerManager = TokenizerManager.getInstance(
3729
- this.cacheDir,
3730
- this.modelsDir
4219
+ this.mediumModelPath = path5.join(
4220
+ this.modelsDir,
4221
+ "DeepSeek-R1-Distill-Qwen-7B-Q5_K_M.gguf"
3731
4222
  );
3732
- this.visionManager = VisionManager.getInstance(this.cacheDir);
3733
- this.transcribeManager = TranscribeManager.getInstance(this.cacheDir);
3734
- this.ttsManager = TTSManager.getInstance(this.cacheDir);
4223
+ const cacheDirEnv = process.env.CACHE_DIR?.trim();
4224
+ if (cacheDirEnv) {
4225
+ this.cacheDir = path5.resolve(cacheDirEnv);
4226
+ } else {
4227
+ const cacheDir = path5.join(process.cwd(), "cache");
4228
+ if (!fs5.existsSync(cacheDir)) {
4229
+ fs5.mkdirSync(cacheDir, { recursive: true });
4230
+ logger10.info("Ensuring cache directory exists:", cacheDir);
4231
+ }
4232
+ this.cacheDir = cacheDir;
4233
+ }
4234
+ this.downloadManager = DownloadManager.getInstance();
4235
+ this.tokenizerManager = TokenizerManager.getInstance();
4236
+ this.visionManager = VisionManager.getInstance();
4237
+ this.transcribeManager = TranscribeManager.getInstance();
4238
+ this.ttsManager = TTSManager.getInstance();
3735
4239
  if (process.env.USE_STUDIOLM_TEXT_MODELS === "true") {
3736
- logger10.info(
3737
- "Creating StudioLM manager instance (enabled in environment)"
3738
- );
3739
4240
  this.studioLMManager = StudioLMManager.getInstance();
3740
- } else {
3741
- logger10.info(
3742
- "StudioLM manager instance not created (disabled in environment)"
3743
- );
3744
4241
  }
3745
4242
  if (process.env.USE_OLLAMA_TEXT_MODELS === "true") {
3746
- logger10.info("Creating Ollama manager instance (enabled in environment)");
3747
4243
  this.ollamaManager = OllamaManager.getInstance();
3748
- } else {
3749
- logger10.info(
3750
- "Ollama manager instance not created (disabled in environment)"
3751
- );
3752
- }
3753
- this.initializeEnvironment().catch((error) => {
3754
- logger10.error("Environment initialization failed:", error);
3755
- throw error;
3756
- });
3757
- this.checkPlatformCapabilities().catch((error) => {
3758
- logger10.warn("Platform capabilities check failed:", error);
3759
- });
3760
- this.initializeEmbedding().catch((error) => {
3761
- logger10.warn("Embedding initialization failed:", error);
3762
- });
3763
- logger10.info("Starting model initialization sequence");
3764
- this.initialize(ModelTypes3.TEXT_SMALL).then(() => {
3765
- logger10.info(
3766
- "Small model initialization complete, starting large model initialization"
3767
- );
3768
- return this.initialize(ModelTypes3.TEXT_LARGE);
3769
- }).catch((error) => {
3770
- logger10.warn("Models initialization failed:", {
3771
- stack: error instanceof Error ? error.stack : void 0,
3772
- error: error instanceof Error ? error.message : String(error)
3773
- });
3774
- });
3775
- const servicePromises = [
3776
- // Add vision initialization using a public method
3777
- this.initializeVision().catch((error) => {
3778
- logger10.warn("Vision initialization failed:", error);
3779
- return null;
3780
- }),
3781
- // Add transcription initialization with better error handling
3782
- this.initializeTranscription().catch((error) => {
3783
- logger10.warn("Transcription initialization failed:", {
3784
- error: error instanceof Error ? error.message : String(error),
3785
- stack: error instanceof Error ? error.stack : void 0
3786
- });
3787
- return null;
3788
- }),
3789
- // Add TTS initialization
3790
- this.initializeTTS().catch((error) => {
3791
- logger10.warn("TTS initialization failed:", {
3792
- error: error instanceof Error ? error.message : String(error),
3793
- stack: error instanceof Error ? error.stack : void 0
3794
- });
3795
- return null;
3796
- })
3797
- ];
3798
- if (process.env.USE_STUDIOLM_TEXT_MODELS === "true" && this.studioLMManager) {
3799
- logger10.info(
3800
- "StudioLM initialization enabled by environment configuration"
3801
- );
3802
- servicePromises.push(
3803
- this.initializeStudioLM().then(() => {
3804
- this.studioLMInitialized = true;
3805
- }).catch((error) => {
3806
- logger10.warn("StudioLM initialization failed:", {
3807
- error: error instanceof Error ? error.message : String(error),
3808
- stack: error instanceof Error ? error.stack : void 0
3809
- });
3810
- return null;
3811
- })
3812
- );
3813
- } else {
3814
- logger10.info(
3815
- "StudioLM initialization skipped (disabled in environment configuration or manager not created)"
3816
- );
3817
- }
3818
- if (process.env.USE_OLLAMA_TEXT_MODELS === "true" && this.ollamaManager) {
3819
- logger10.info("Ollama initialization enabled by environment configuration");
3820
- servicePromises.push(
3821
- this.initializeOllama().then(() => {
3822
- this.ollamaInitialized = true;
3823
- }).catch((error) => {
3824
- logger10.warn("Ollama initialization failed:", {
3825
- error: error instanceof Error ? error.message : String(error),
3826
- stack: error instanceof Error ? error.stack : void 0
3827
- });
3828
- return null;
3829
- })
3830
- );
3831
- } else {
3832
- logger10.info(
3833
- "Ollama initialization skipped (disabled in environment configuration or manager not created)"
3834
- );
3835
4244
  }
3836
- Promise.all(servicePromises).catch((error) => {
3837
- logger10.warn("Models initialization failed:", {
3838
- error: error instanceof Error ? error.message : String(error),
3839
- stack: error instanceof Error ? error.stack : void 0
3840
- });
3841
- });
4245
+ this.activeModelConfig = MODEL_SPECS.small;
3842
4246
  }
4247
+ /**
4248
+ * Retrieves the singleton instance of LocalAIManager. If an instance does not already exist, a new one is created and returned.
4249
+ * @returns {LocalAIManager} The singleton instance of LocalAIManager
4250
+ */
3843
4251
  static getInstance() {
3844
4252
  if (!_LocalAIManager.instance) {
3845
4253
  _LocalAIManager.instance = new _LocalAIManager();
3846
4254
  }
3847
4255
  return _LocalAIManager.instance;
3848
4256
  }
4257
+ /**
4258
+ * Initializes the environment by validating the configuration and setting the environment variables with the validated values.
4259
+ *
4260
+ * @returns {Promise<void>} A Promise that resolves once the environment has been successfully initialized.
4261
+ */
3849
4262
  async initializeEnvironment() {
3850
4263
  try {
3851
4264
  logger10.info("Validating environment configuration...");
@@ -3872,6 +4285,12 @@ var LocalAIManager = class _LocalAIManager {
3872
4285
  throw error;
3873
4286
  }
3874
4287
  }
4288
+ /**
4289
+ * Asynchronously initializes the Ollama model.
4290
+ *
4291
+ * @returns {Promise<void>} A Promise that resolves when the initialization is complete.
4292
+ * @throws {Error} If the Ollama manager is not created, or if initialization of Ollama models fails.
4293
+ */
3875
4294
  async initializeOllama() {
3876
4295
  try {
3877
4296
  logger10.info("Initializing Ollama models...");
@@ -3894,6 +4313,11 @@ var LocalAIManager = class _LocalAIManager {
3894
4313
  throw error;
3895
4314
  }
3896
4315
  }
4316
+ /**
4317
+ * Initializes StudioLM model with error handling.
4318
+ * @returns A Promise that resolves when the initialization is complete.
4319
+ * @throws {Error} If StudioLM manager is not created, initialization fails, or models are not properly loaded.
4320
+ */
3897
4321
  async initializeStudioLM() {
3898
4322
  try {
3899
4323
  logger10.info("Initializing StudioLM models...");
@@ -3917,130 +4341,12 @@ var LocalAIManager = class _LocalAIManager {
3917
4341
  throw error;
3918
4342
  }
3919
4343
  }
3920
- async initializeTranscription() {
3921
- try {
3922
- logger10.info("Initializing transcription model...");
3923
- const ffmpegAvailable = await this.transcribeManager.ensureFFmpeg();
3924
- if (!ffmpegAvailable) {
3925
- throw new Error(
3926
- "Cannot initialize transcription without FFmpeg. Please install FFmpeg and try again."
3927
- );
3928
- }
3929
- const samplePath = path5.join(this.cacheDir, "sample1.wav");
3930
- const awsSampleUrl = "https://d2908q01vomqb2.cloudfront.net/artifacts/DBSBlogs/ML-15311/sample1.wav?_=1";
3931
- if (!fs5.existsSync(samplePath)) {
3932
- logger10.info(
3933
- "Sample WAV file not found in cache, downloading from AWS..."
3934
- );
3935
- try {
3936
- await this.downloadManager.downloadFromUrl(awsSampleUrl, samplePath);
3937
- logger10.success("Sample WAV file downloaded successfully");
3938
- } catch (downloadError) {
3939
- logger10.error("Failed to download sample WAV file:", {
3940
- error: downloadError instanceof Error ? downloadError.message : String(downloadError),
3941
- url: awsSampleUrl,
3942
- destination: samplePath,
3943
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3944
- });
3945
- throw downloadError;
3946
- }
3947
- } else {
3948
- logger10.info("Sample WAV file already exists in cache");
3949
- }
3950
- if (!fs5.existsSync(samplePath)) {
3951
- throw new Error(
3952
- `Sample audio file not found at: ${samplePath} after download attempt`
3953
- );
3954
- }
3955
- const testAudioBuffer = fs5.readFileSync(samplePath);
3956
- const result = await this.transcribeAudio(testAudioBuffer);
3957
- logger10.info("Test transcription result:", {
3958
- text: result,
3959
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3960
- });
3961
- logger10.success("Transcription model initialization complete");
3962
- } catch (error) {
3963
- logger10.error("Transcription initialization failed:", {
3964
- error: error instanceof Error ? error.message : String(error),
3965
- stack: error instanceof Error ? error.stack : void 0,
3966
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
3967
- });
3968
- throw error;
3969
- }
3970
- }
3971
- async initializeVision() {
3972
- try {
3973
- logger10.info("Initializing vision model...");
3974
- const awsImageUrl = "https://d1.awsstatic.com/product-marketing/Rekognition/Image%20for%20facial%20analysis.3fcc22e8451b4a238540128cb5510b8cbe22da51.jpg";
3975
- const imagePath = path5.join(this.cacheDir, "test_image.jpg");
3976
- if (!fs5.existsSync(imagePath)) {
3977
- logger10.info("Downloading test image from AWS...");
3978
- try {
3979
- await this.downloadManager.downloadFromUrl(awsImageUrl, imagePath);
3980
- logger10.success("Test image downloaded successfully");
3981
- } catch (downloadError) {
3982
- logger10.error("Failed to download test image:", {
3983
- error: downloadError instanceof Error ? downloadError.message : String(downloadError),
3984
- url: awsImageUrl,
3985
- destination: imagePath
3986
- });
3987
- throw downloadError;
3988
- }
3989
- } else {
3990
- logger10.info("Test image already exists in cache");
3991
- }
3992
- if (!fs5.existsSync(imagePath)) {
3993
- throw new Error(
3994
- `Test image not found at: ${imagePath} after download attempt`
3995
- );
3996
- }
3997
- const imageBuffer = fs5.readFileSync(imagePath);
3998
- const result = await this.describeImage(imageBuffer, "image/jpeg");
3999
- logger10.info("Test image description:", result);
4000
- logger10.success("Vision model initialization complete");
4001
- } catch (error) {
4002
- logger10.error("Vision initialization failed:", error);
4003
- throw error;
4004
- }
4005
- }
4006
- async initializeTTS() {
4007
- try {
4008
- logger10.info("Initializing TTS model...");
4009
- const testText = "ElizaOS is yours";
4010
- logger10.info("Testing TTS with sample text:", { text: testText });
4011
- const audioStream = await this.ttsManager.generateSpeech(testText);
4012
- if (!(audioStream instanceof Readable2)) {
4013
- throw new Error("TTS did not return a valid audio stream");
4014
- }
4015
- let dataReceived = false;
4016
- await new Promise((resolve, reject) => {
4017
- audioStream.on("data", () => {
4018
- if (!dataReceived) {
4019
- dataReceived = true;
4020
- logger10.info("TTS audio stream is producing data");
4021
- }
4022
- });
4023
- audioStream.on("end", () => {
4024
- if (!dataReceived) {
4025
- reject(new Error("No audio data received from TTS stream"));
4026
- } else {
4027
- resolve();
4028
- }
4029
- });
4030
- audioStream.on("error", (err) => {
4031
- reject(err);
4032
- });
4033
- });
4034
- logger10.success("TTS model initialization complete");
4035
- } catch (error) {
4036
- logger10.error("TTS initialization failed:", {
4037
- error: error instanceof Error ? error.message : String(error),
4038
- stack: error instanceof Error ? error.stack : void 0,
4039
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4040
- });
4041
- throw error;
4042
- }
4043
- }
4344
+ /**
4345
+ * Downloads the model based on the modelPath provided.
4346
+ * Determines whether to download a large or small model based on the current modelPath.
4347
+ *
4348
+ * @returns A Promise that resolves to a boolean indicating whether the model download was successful.
4349
+ */
4044
4350
  async downloadModel() {
4045
4351
  try {
4046
4352
  const isLargeModel = this.modelPath === this.mediumModelPath;
@@ -4057,6 +4363,11 @@ var LocalAIManager = class _LocalAIManager {
4057
4363
  throw error;
4058
4364
  }
4059
4365
  }
4366
+ /**
4367
+ * Asynchronously checks the platform capabilities.
4368
+ *
4369
+ * @returns {Promise<void>} A promise that resolves once the platform capabilities have been checked.
4370
+ */
4060
4371
  async checkPlatformCapabilities() {
4061
4372
  try {
4062
4373
  const platformManager = getPlatformManager();
@@ -4072,65 +4383,24 @@ var LocalAIManager = class _LocalAIManager {
4072
4383
  logger10.warn("Platform detection failed:", error);
4073
4384
  }
4074
4385
  }
4386
+ /**
4387
+ * Initializes the LocalAI Manager for a given model type.
4388
+ *
4389
+ * @param {ModelType} modelType - The type of model to initialize (default: ModelTypes.TEXT_SMALL)
4390
+ * @returns {Promise<void>} A promise that resolves when initialization is complete or rejects if an error occurs
4391
+ */
4075
4392
  async initialize(modelType = ModelTypes3.TEXT_SMALL) {
4076
- try {
4077
- logger10.info("Initializing LocalAI Manager for model class:", modelType);
4078
- if (modelType === ModelTypes3.TEXT_LARGE) {
4079
- this.modelPath = this.mediumModelPath;
4080
- logger10.info("Using medium model path:", this.modelPath);
4081
- } else {
4082
- this.modelPath = path5.join(this.modelsDir, MODEL_SPECS.small.name);
4083
- logger10.info("Using small model path:", this.modelPath);
4084
- }
4085
- const wasNewlyDownloaded = await this.downloadModel();
4086
- if (wasNewlyDownloaded) {
4087
- if (modelType === ModelTypes3.TEXT_LARGE) {
4088
- logger10.info(
4089
- "Adding delay before loading large model to ensure download is complete..."
4090
- );
4091
- await new Promise((resolve) => setTimeout(resolve, 1e4));
4092
- } else {
4093
- logger10.info(
4094
- "Adding delay before loading small model to ensure download is complete..."
4095
- );
4096
- await new Promise((resolve) => setTimeout(resolve, 1e4));
4097
- }
4098
- }
4099
- if (!fs5.existsSync(this.modelPath)) {
4100
- throw new Error(`Model file not found at path: ${this.modelPath}`);
4101
- }
4102
- this.llama = await getLlama2();
4103
- if (modelType === ModelTypes3.TEXT_LARGE) {
4104
- this.activeModelConfig = MODEL_SPECS.medium;
4105
- logger10.info("Loading large model from:", this.modelPath);
4106
- this.mediumModel = await this.llama.loadModel({
4107
- modelPath: this.modelPath
4108
- });
4109
- this.ctx = await this.mediumModel.createContext({
4110
- contextSize: MODEL_SPECS.medium.contextSize
4111
- });
4112
- } else {
4113
- this.activeModelConfig = MODEL_SPECS.small;
4114
- logger10.info("Loading small model from:", this.modelPath);
4115
- this.smallModel = await this.llama.loadModel({
4116
- modelPath: this.modelPath
4117
- });
4118
- this.ctx = await this.smallModel.createContext({
4119
- contextSize: MODEL_SPECS.small.contextSize
4120
- });
4121
- }
4122
- if (!this.ctx) {
4123
- throw new Error("Failed to create prompt");
4124
- }
4125
- this.sequence = this.ctx.getSequence();
4126
- logger10.success(
4127
- `Model initialization complete for ${modelType === ModelTypes3.TEXT_LARGE ? "large" : "small"} model`
4128
- );
4129
- } catch (error) {
4130
- logger10.error("Initialization failed:", error);
4131
- throw error;
4393
+ if (modelType === ModelTypes3.TEXT_LARGE) {
4394
+ await this.lazyInitMediumModel();
4395
+ } else {
4396
+ await this.lazyInitSmallModel();
4132
4397
  }
4133
4398
  }
4399
+ /**
4400
+ * Asynchronously initializes the embedding model.
4401
+ *
4402
+ * @returns {Promise<void>} A promise that resolves once the initialization is complete.
4403
+ */
4134
4404
  async initializeEmbedding() {
4135
4405
  try {
4136
4406
  logger10.info("Initializing embedding model...");
@@ -4159,13 +4429,6 @@ var LocalAIManager = class _LocalAIManager {
4159
4429
  logger10.info(`Downloading embedding model: ${completedBar} 100%`);
4160
4430
  logger10.success("FlagEmbedding instance created successfully");
4161
4431
  }
4162
- logger10.info("Testing embedding model with sample text...");
4163
- const testEmbed = await this.embeddingModel.queryEmbed("test");
4164
- logger10.info(
4165
- "Test embedding generated successfully, dimensions:",
4166
- testEmbed.length
4167
- );
4168
- logger10.success("Embedding model initialization complete");
4169
4432
  } catch (error) {
4170
4433
  logger10.error("Embedding initialization failed with details:", {
4171
4434
  error: error instanceof Error ? error.message : String(error),
@@ -4176,6 +4439,12 @@ var LocalAIManager = class _LocalAIManager {
4176
4439
  throw error;
4177
4440
  }
4178
4441
  }
4442
+ /**
4443
+ * Asynchronously generates text using either StudioLM or Ollama models based on the specified parameters.
4444
+ *
4445
+ * @param {GenerateTextParams} params - The parameters for generating the text.
4446
+ * @returns {Promise<string>} - A promise that resolves to the generated text.
4447
+ */
4179
4448
  async generateTextOllamaStudio(params) {
4180
4449
  try {
4181
4450
  const modelConfig = this.getTextModelSource();
@@ -4242,28 +4511,30 @@ var LocalAIManager = class _LocalAIManager {
4242
4511
  return this.generateText(params);
4243
4512
  }
4244
4513
  }
4514
+ /**
4515
+ * Asynchronously generates text based on the provided parameters.
4516
+ * Now uses lazy initialization for models
4517
+ */
4245
4518
  async generateText(params) {
4246
4519
  try {
4247
- if (!this.sequence || !this.smallModel || params.modelType === ModelTypes3.TEXT_LARGE && !this.mediumModel) {
4248
- await this.initialize(params.modelType);
4249
- }
4250
- let activeModel;
4251
4520
  if (params.modelType === ModelTypes3.TEXT_LARGE) {
4521
+ await this.lazyInitMediumModel();
4252
4522
  if (!this.mediumModel) {
4253
- throw new Error("Medium model not initialized");
4523
+ throw new Error("Medium model initialization failed");
4254
4524
  }
4255
4525
  this.activeModelConfig = MODEL_SPECS.medium;
4256
- activeModel = this.mediumModel;
4257
- this.ctx = await activeModel.createContext({
4526
+ const mediumModel = this.mediumModel;
4527
+ this.ctx = await mediumModel.createContext({
4258
4528
  contextSize: MODEL_SPECS.medium.contextSize
4259
4529
  });
4260
4530
  } else {
4531
+ await this.lazyInitSmallModel();
4261
4532
  if (!this.smallModel) {
4262
- throw new Error("Small model not initialized");
4533
+ throw new Error("Small model initialization failed");
4263
4534
  }
4264
4535
  this.activeModelConfig = MODEL_SPECS.small;
4265
- activeModel = this.smallModel;
4266
- this.ctx = await activeModel.createContext({
4536
+ const smallModel = this.smallModel;
4537
+ this.ctx = await smallModel.createContext({
4267
4538
  contextSize: MODEL_SPECS.small.contextSize
4268
4539
  });
4269
4540
  }
@@ -4300,7 +4571,7 @@ var LocalAIManager = class _LocalAIManager {
4300
4571
  temperature: 0.7,
4301
4572
  topP: 0.9,
4302
4573
  repeatPenalty: {
4303
- punishTokensFilter: () => activeModel.tokenize(wordsToPunish.join(" ")),
4574
+ punishTokensFilter: () => this.smallModel.tokenize(wordsToPunish.join(" ")),
4304
4575
  penalty: 1.2,
4305
4576
  frequencyPenalty: 0.7,
4306
4577
  presencePenalty: 0.7
@@ -4322,19 +4593,12 @@ var LocalAIManager = class _LocalAIManager {
4322
4593
  throw error;
4323
4594
  }
4324
4595
  }
4596
+ /**
4597
+ * Generate embeddings - now with lazy initialization
4598
+ */
4325
4599
  async generateEmbedding(text) {
4326
4600
  try {
4327
- logger10.info("Generating embedding...");
4328
- if (!text) {
4329
- throw new Error("Input text cannot be null or undefined");
4330
- }
4331
- logger10.debug("Input text length:", text.length);
4332
- if (!this.embeddingModel) {
4333
- logger10.error(
4334
- "Embedding model not initialized, attempting to initialize..."
4335
- );
4336
- await this.initializeEmbedding();
4337
- }
4601
+ await this.lazyInitEmbedding();
4338
4602
  if (!this.embeddingModel) {
4339
4603
  throw new Error("Failed to initialize embedding model");
4340
4604
  }
@@ -4353,8 +4617,12 @@ var LocalAIManager = class _LocalAIManager {
4353
4617
  throw error;
4354
4618
  }
4355
4619
  }
4620
+ /**
4621
+ * Describe image with lazy vision model initialization
4622
+ */
4356
4623
  async describeImage(imageData, mimeType) {
4357
4624
  try {
4625
+ await this.lazyInitVision();
4358
4626
  const base64 = imageData.toString("base64");
4359
4627
  const dataUrl = `data:${mimeType};base64,${base64}`;
4360
4628
  return await this.visionManager.processImage(dataUrl);
@@ -4363,8 +4631,12 @@ var LocalAIManager = class _LocalAIManager {
4363
4631
  throw error;
4364
4632
  }
4365
4633
  }
4634
+ /**
4635
+ * Transcribe audio with lazy transcription model initialization
4636
+ */
4366
4637
  async transcribeAudio(audioBuffer) {
4367
4638
  try {
4639
+ await this.lazyInitTranscription();
4368
4640
  const result = await this.transcribeManager.transcribe(audioBuffer);
4369
4641
  return result.text;
4370
4642
  } catch (error) {
@@ -4375,8 +4647,12 @@ var LocalAIManager = class _LocalAIManager {
4375
4647
  throw error;
4376
4648
  }
4377
4649
  }
4650
+ /**
4651
+ * Generate speech with lazy TTS model initialization
4652
+ */
4378
4653
  async generateSpeech(text) {
4379
4654
  try {
4655
+ await this.lazyInitTTS();
4380
4656
  return await this.ttsManager.generateSpeech(text);
4381
4657
  } catch (error) {
4382
4658
  logger10.error("Speech generation failed:", {
@@ -4387,12 +4663,25 @@ var LocalAIManager = class _LocalAIManager {
4387
4663
  }
4388
4664
  }
4389
4665
  // Add public accessor methods
4666
+ /**
4667
+ * Returns the TokenizerManager associated with this object.
4668
+ *
4669
+ * @returns {TokenizerManager} The TokenizerManager object.
4670
+ */
4390
4671
  getTokenizerManager() {
4391
4672
  return this.tokenizerManager;
4392
4673
  }
4674
+ /**
4675
+ * Returns the active model configuration.
4676
+ * @returns {ModelSpec} The active model configuration.
4677
+ */
4393
4678
  getActiveModelConfig() {
4394
4679
  return this.activeModelConfig;
4395
4680
  }
4681
+ /**
4682
+ * Retrieves the source configuration for the text model based on environment variables and manager existence.
4683
+ * @returns {TextModelConfig} The configuration object containing the text model source and type.
4684
+ */
4396
4685
  getTextModelSource() {
4397
4686
  try {
4398
4687
  const config = {
@@ -4411,19 +4700,211 @@ var LocalAIManager = class _LocalAIManager {
4411
4700
  return { source: "local", modelType: ModelTypes3.TEXT_SMALL };
4412
4701
  }
4413
4702
  }
4703
+ /**
4704
+ * Generic lazy initialization handler for any model type
4705
+ */
4706
+ async lazyInitialize(modelType, isInitialized, initPromise, initFunction) {
4707
+ if (isInitialized) {
4708
+ return Promise.resolve(null);
4709
+ }
4710
+ if (initPromise) {
4711
+ logger10.info(`Waiting for ${modelType} initialization to complete...`);
4712
+ await initPromise;
4713
+ return Promise.resolve(null);
4714
+ }
4715
+ logger10.info(`Lazy initializing ${modelType}...`);
4716
+ return initFunction();
4717
+ }
4718
+ /**
4719
+ * Lazy initialize the small text model
4720
+ */
4721
+ async lazyInitSmallModel() {
4722
+ if (this.smallModelInitialized) return;
4723
+ if (!this.smallModelInitializingPromise) {
4724
+ this.smallModelInitializingPromise = (async () => {
4725
+ await this.initializeEnvironment();
4726
+ await this.checkPlatformCapabilities();
4727
+ await this.downloadModel();
4728
+ try {
4729
+ this.llama = await getLlama2();
4730
+ const smallModel = await this.llama.loadModel(this.modelPath, {
4731
+ gpuLayers: 43,
4732
+ embeddings: true,
4733
+ vocabOnly: false
4734
+ });
4735
+ this.smallModel = smallModel;
4736
+ const ctx = await smallModel.createContext({
4737
+ contextSize: MODEL_SPECS.small.contextSize
4738
+ });
4739
+ this.ctx = ctx;
4740
+ this.sequence = void 0;
4741
+ this.smallModelInitialized = true;
4742
+ logger10.info("Small model initialized successfully");
4743
+ } catch (error) {
4744
+ logger10.error("Failed to initialize small model:", error);
4745
+ this.smallModelInitializingPromise = null;
4746
+ throw error;
4747
+ }
4748
+ })();
4749
+ }
4750
+ await this.smallModelInitializingPromise;
4751
+ }
4752
+ /**
4753
+ * Lazy initialize the medium text model
4754
+ */
4755
+ async lazyInitMediumModel() {
4756
+ if (this.mediumModelInitialized) return;
4757
+ if (!this.mediumModelInitializingPromise) {
4758
+ this.mediumModelInitializingPromise = (async () => {
4759
+ if (!this.llama) {
4760
+ await this.lazyInitSmallModel();
4761
+ }
4762
+ try {
4763
+ const mediumModel = await this.llama.loadModel(
4764
+ this.mediumModelPath,
4765
+ {
4766
+ gpuLayers: 43,
4767
+ embeddings: true,
4768
+ vocabOnly: false
4769
+ }
4770
+ );
4771
+ this.mediumModel = mediumModel;
4772
+ this.mediumModelInitialized = true;
4773
+ logger10.info("Medium model initialized successfully");
4774
+ } catch (error) {
4775
+ logger10.error("Failed to initialize medium model:", error);
4776
+ this.mediumModelInitializingPromise = null;
4777
+ throw error;
4778
+ }
4779
+ })();
4780
+ }
4781
+ await this.mediumModelInitializingPromise;
4782
+ }
4783
+ /**
4784
+ * Lazy initialize the embedding model
4785
+ */
4786
+ async lazyInitEmbedding() {
4787
+ if (this.embeddingInitialized) return;
4788
+ if (!this.embeddingInitializingPromise) {
4789
+ this.embeddingInitializingPromise = (async () => {
4790
+ try {
4791
+ await this.initializeEmbedding();
4792
+ this.embeddingInitialized = true;
4793
+ logger10.info("Embedding model initialized successfully");
4794
+ } catch (error) {
4795
+ logger10.error("Failed to initialize embedding model:", error);
4796
+ this.embeddingInitializingPromise = null;
4797
+ throw error;
4798
+ }
4799
+ })();
4800
+ }
4801
+ await this.embeddingInitializingPromise;
4802
+ }
4803
+ /**
4804
+ * Lazy initialize the vision model
4805
+ */
4806
+ async lazyInitVision() {
4807
+ if (this.visionInitialized) return;
4808
+ if (!this.visionInitializingPromise) {
4809
+ this.visionInitializingPromise = (async () => {
4810
+ try {
4811
+ this.visionInitialized = true;
4812
+ logger10.info("Vision model initialized successfully");
4813
+ } catch (error) {
4814
+ logger10.error("Failed to initialize vision model:", error);
4815
+ this.visionInitializingPromise = null;
4816
+ throw error;
4817
+ }
4818
+ })();
4819
+ }
4820
+ await this.visionInitializingPromise;
4821
+ }
4822
+ /**
4823
+ * Lazy initialize the transcription model
4824
+ */
4825
+ async lazyInitTranscription() {
4826
+ if (this.transcriptionInitialized) return;
4827
+ if (!this.transcriptionInitializingPromise) {
4828
+ this.transcriptionInitializingPromise = (async () => {
4829
+ try {
4830
+ this.transcriptionInitialized = true;
4831
+ logger10.info("Transcription model initialized successfully");
4832
+ } catch (error) {
4833
+ logger10.error("Failed to initialize transcription model:", error);
4834
+ this.transcriptionInitializingPromise = null;
4835
+ throw error;
4836
+ }
4837
+ })();
4838
+ }
4839
+ await this.transcriptionInitializingPromise;
4840
+ }
4841
+ /**
4842
+ * Lazy initialize the TTS model
4843
+ */
4844
+ async lazyInitTTS() {
4845
+ if (this.ttsInitialized) return;
4846
+ if (!this.ttsInitializingPromise) {
4847
+ this.ttsInitializingPromise = (async () => {
4848
+ try {
4849
+ this.ttsInitialized = true;
4850
+ logger10.info("TTS model initialized successfully");
4851
+ } catch (error) {
4852
+ logger10.error("Failed to initialize TTS model:", error);
4853
+ this.ttsInitializingPromise = null;
4854
+ throw error;
4855
+ }
4856
+ })();
4857
+ }
4858
+ await this.ttsInitializingPromise;
4859
+ }
4860
+ /**
4861
+ * Lazy initialize the Ollama integration
4862
+ */
4863
+ async lazyInitOllama() {
4864
+ if (this.ollamaInitialized) return;
4865
+ if (!this.ollamaInitializingPromise) {
4866
+ this.ollamaInitializingPromise = (async () => {
4867
+ try {
4868
+ await this.initializeOllama();
4869
+ this.ollamaInitialized = true;
4870
+ logger10.info("Ollama initialized successfully");
4871
+ } catch (error) {
4872
+ logger10.error("Failed to initialize Ollama:", error);
4873
+ this.ollamaInitializingPromise = null;
4874
+ throw error;
4875
+ }
4876
+ })();
4877
+ }
4878
+ await this.ollamaInitializingPromise;
4879
+ }
4880
+ /**
4881
+ * Lazy initialize the StudioLM integration
4882
+ */
4883
+ async lazyInitStudioLM() {
4884
+ if (this.studioLMInitialized) return;
4885
+ if (!this.studioLMInitializingPromise) {
4886
+ this.studioLMInitializingPromise = (async () => {
4887
+ try {
4888
+ await this.initializeStudioLM();
4889
+ this.studioLMInitialized = true;
4890
+ logger10.info("StudioLM initialized successfully");
4891
+ } catch (error) {
4892
+ logger10.error("Failed to initialize StudioLM:", error);
4893
+ this.studioLMInitializingPromise = null;
4894
+ throw error;
4895
+ }
4896
+ })();
4897
+ }
4898
+ await this.studioLMInitializingPromise;
4899
+ }
4414
4900
  };
4415
4901
  var localAIManager = LocalAIManager.getInstance();
4416
4902
  var localAIPlugin = {
4417
4903
  name: "local-ai",
4418
4904
  description: "Local AI plugin using LLaMA models",
4419
- async init(config) {
4905
+ async init() {
4420
4906
  try {
4421
4907
  logger10.info("Initializing local-ai plugin...");
4422
- const validatedConfig = await validateConfig(config);
4423
- for (const [key, value] of Object.entries(validatedConfig)) {
4424
- process.env[key] = String(value);
4425
- logger10.debug(`Set ${key}=${value}`);
4426
- }
4427
4908
  logger10.success("Local AI plugin configuration validated and initialized");
4428
4909
  } catch (error) {
4429
4910
  logger10.error("Plugin initialization failed:", {