open-agents-ai 0.103.80 → 0.103.82

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.
Files changed (2) hide show
  1. package/dist/index.js +199 -77
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -38747,9 +38747,19 @@ var init_voice = __esm({
38747
38747
  * Returns status message.
38748
38748
  */
38749
38749
  async setCloneVoice(audioPath) {
38750
- if (!existsSync34(audioPath)) {
38751
- return `File not found: ${audioPath}`;
38750
+ let p = audioPath.trim();
38751
+ if (p.startsWith("'") && p.endsWith("'") || p.startsWith('"') && p.endsWith('"')) {
38752
+ p = p.slice(1, -1);
38752
38753
  }
38754
+ p = p.replace(/\\ /g, " ");
38755
+ if (p.startsWith("~/") || p === "~") {
38756
+ p = join43(homedir12(), p.slice(1));
38757
+ }
38758
+ if (!existsSync34(p)) {
38759
+ return `File not found: ${p}
38760
+ (original input: ${audioPath})`;
38761
+ }
38762
+ audioPath = p;
38753
38763
  const refsDir = luxttsCloneRefsDir();
38754
38764
  if (!existsSync34(refsDir))
38755
38765
  mkdirSync14(refsDir, { recursive: true });
@@ -38957,6 +38967,19 @@ var init_voice = __esm({
38957
38967
  this.config = null;
38958
38968
  this.mlxActive = false;
38959
38969
  this.luxttsActive = false;
38970
+ if (this._luxttsDaemon && !this._luxttsDaemon.killed) {
38971
+ try {
38972
+ this._luxttsDaemon.stdin.write('{"action":"quit"}\n');
38973
+ } catch {
38974
+ }
38975
+ setTimeout(() => {
38976
+ try {
38977
+ this._luxttsDaemon?.kill();
38978
+ } catch {
38979
+ }
38980
+ }, 2e3);
38981
+ }
38982
+ this._luxttsDaemon = null;
38960
38983
  }
38961
38984
  // -------------------------------------------------------------------------
38962
38985
  // Text chunking for long TTS input
@@ -39610,70 +39633,95 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
39610
39633
  }
39611
39634
  }
39612
39635
  }
39613
- /** Write the LuxTTS inference helper Python script */
39636
+ /** Persistent LuxTTS daemon process keeps model warm in GPU memory */
39637
+ _luxttsDaemon = null;
39638
+ _luxttsPending = /* @__PURE__ */ new Map();
39639
+ _luxttsBuffer = "";
39640
+ /** Write the LuxTTS persistent daemon Python script */
39614
39641
  writeLuxttsInferScript() {
39615
39642
  const script = `#!/usr/bin/env python3
39616
- """LuxTTS inference helper for open-agents voice engine.
39617
- Called by voice.ts via execSync with JSON args.
39643
+ """LuxTTS persistent daemon for open-agents voice engine.
39644
+ Keeps model warm in GPU memory. Reads JSON requests from stdin, writes JSON responses to stdout.
39618
39645
  """
39619
- import sys, json, os, wave, struct
39646
+ import sys, json, os, wave, signal
39620
39647
  import numpy as np
39621
39648
 
39622
39649
  def main():
39623
- args = json.loads(sys.argv[1])
39624
- action = args.get('action', 'synthesize')
39625
-
39626
- repo_path = args.get('repo_path', '')
39650
+ repo_path = os.environ.get('LUXTTS_REPO_PATH', '')
39627
39651
  if repo_path:
39628
39652
  sys.path.insert(0, repo_path)
39629
39653
 
39630
- if action == 'check':
39654
+ import torch
39655
+ from zipvoice.luxvoice import LuxTTS
39656
+
39657
+ # Auto-detect best device
39658
+ if torch.cuda.is_available():
39659
+ device = 'cuda'
39660
+ elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
39661
+ device = 'mps'
39662
+ else:
39663
+ device = 'cpu'
39664
+
39665
+ # Load model ONCE \u2014 stays warm in GPU/CPU memory
39666
+ tts = LuxTTS(model_path='YatharthS/LuxTTS', device=device, threads=4)
39667
+ sys.stdout.write(json.dumps({'type': 'ready', 'device': device}) + '\\n')
39668
+ sys.stdout.flush()
39669
+
39670
+ # Cache encoded prompts by clone_ref path (avoid re-encoding same voice)
39671
+ prompt_cache = {}
39672
+
39673
+ for line in sys.stdin:
39674
+ line = line.strip()
39675
+ if not line:
39676
+ continue
39631
39677
  try:
39632
- from zipvoice.luxvoice import LuxTTS
39633
- print(json.dumps({'ok': True}))
39678
+ req = json.loads(line)
39634
39679
  except Exception as e:
39635
- print(json.dumps({'ok': False, 'error': str(e)}))
39636
- return
39637
-
39638
- if action == 'synthesize':
39639
- import torch
39640
- from zipvoice.luxvoice import LuxTTS
39641
-
39642
- text = args['text']
39643
- clone_ref = args['clone_ref']
39644
- output_path = args['output_path']
39645
- speed = args.get('speed', 1.0)
39646
-
39647
- # Auto-detect best device
39648
- if torch.cuda.is_available():
39649
- device = 'cuda'
39650
- elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
39651
- device = 'mps'
39652
- else:
39653
- device = 'cpu'
39654
-
39655
- threads = 4 if device == 'cpu' else 4
39656
- tts = LuxTTS(model_path='YatharthS/LuxTTS', device=device, threads=threads)
39657
- encoded = tts.encode_prompt(clone_ref, duration=5, rms=0.001)
39658
- wav_tensor = tts.generate_speech(text, encoded, num_steps=4, guidance_scale=3.0, t_shift=0.5, speed=speed)
39659
- wav_np = wav_tensor.cpu().numpy().squeeze()
39660
-
39661
- # Write as 48kHz 16-bit mono WAV using stdlib
39662
- int16_data = (np.clip(wav_np, -1.0, 1.0) * 32767).astype(np.int16)
39663
- with wave.open(output_path, 'wb') as wf:
39664
- wf.setnchannels(1)
39665
- wf.setsampwidth(2)
39666
- wf.setframerate(48000)
39667
- wf.writeframes(int16_data.tobytes())
39668
-
39669
- print(json.dumps({
39670
- 'ok': True,
39671
- 'path': output_path,
39672
- 'sample_rate': 48000,
39673
- 'device': device,
39674
- 'samples': len(int16_data),
39675
- }))
39676
- return
39680
+ sys.stdout.write(json.dumps({'type': 'error', 'id': '', 'error': str(e)}) + '\\n')
39681
+ sys.stdout.flush()
39682
+ continue
39683
+
39684
+ req_id = req.get('id', '')
39685
+
39686
+ if req.get('action') == 'check':
39687
+ sys.stdout.write(json.dumps({'type': 'result', 'id': req_id, 'ok': True}) + '\\n')
39688
+ sys.stdout.flush()
39689
+ continue
39690
+
39691
+ if req.get('action') == 'quit':
39692
+ break
39693
+
39694
+ if req.get('action') == 'synthesize':
39695
+ try:
39696
+ text = req['text']
39697
+ clone_ref = req['clone_ref']
39698
+ output_path = req['output_path']
39699
+ speed = req.get('speed', 1.0)
39700
+
39701
+ # Use cached prompt encoding if available
39702
+ if clone_ref not in prompt_cache:
39703
+ prompt_cache[clone_ref] = tts.encode_prompt(clone_ref, duration=5, rms=0.001)
39704
+ encoded = prompt_cache[clone_ref]
39705
+
39706
+ wav_tensor = tts.generate_speech(text, encoded, num_steps=4, guidance_scale=3.0, t_shift=0.5, speed=speed)
39707
+ wav_np = wav_tensor.cpu().numpy().squeeze()
39708
+
39709
+ int16_data = (np.clip(wav_np, -1.0, 1.0) * 32767).astype(np.int16)
39710
+ with wave.open(output_path, 'wb') as wf:
39711
+ wf.setnchannels(1)
39712
+ wf.setsampwidth(2)
39713
+ wf.setframerate(48000)
39714
+ wf.writeframes(int16_data.tobytes())
39715
+
39716
+ sys.stdout.write(json.dumps({
39717
+ 'type': 'result', 'id': req_id, 'ok': True,
39718
+ 'path': output_path, 'sample_rate': 48000,
39719
+ 'device': device, 'samples': len(int16_data),
39720
+ }) + '\\n')
39721
+ sys.stdout.flush()
39722
+ except Exception as e:
39723
+ sys.stdout.write(json.dumps({'type': 'error', 'id': req_id, 'error': str(e)}) + '\\n')
39724
+ sys.stdout.flush()
39677
39725
 
39678
39726
  if __name__ == '__main__':
39679
39727
  main()
@@ -39682,6 +39730,84 @@ if __name__ == '__main__':
39682
39730
  mkdirSync14(voiceDir(), { recursive: true });
39683
39731
  writeFileSync14(scriptPath2, script);
39684
39732
  }
39733
+ /** Ensure the LuxTTS daemon is running, spawn if needed */
39734
+ async ensureLuxttsDaemon() {
39735
+ if (this._luxttsDaemon && !this._luxttsDaemon.killed)
39736
+ return true;
39737
+ const venvPy = luxttsVenvPy();
39738
+ if (!existsSync34(venvPy))
39739
+ return false;
39740
+ return new Promise((resolve31) => {
39741
+ const env = { ...process.env, LUXTTS_REPO_PATH: luxttsRepoDir() };
39742
+ const daemon = nodeSpawn(venvPy, [luxttsInferScript()], {
39743
+ stdio: ["pipe", "pipe", "pipe"],
39744
+ cwd: tmpdir6(),
39745
+ env
39746
+ });
39747
+ this._luxttsDaemon = daemon;
39748
+ this._luxttsBuffer = "";
39749
+ daemon.stdout.on("data", (chunk) => {
39750
+ this._luxttsBuffer += chunk.toString();
39751
+ const lines = this._luxttsBuffer.split("\n");
39752
+ this._luxttsBuffer = lines.pop();
39753
+ for (const line of lines) {
39754
+ if (!line.trim())
39755
+ continue;
39756
+ try {
39757
+ const msg = JSON.parse(line);
39758
+ if (msg.type === "ready") {
39759
+ resolve31(true);
39760
+ } else if (msg.type === "result" || msg.type === "error") {
39761
+ const pending = this._luxttsPending.get(msg.id);
39762
+ if (pending) {
39763
+ this._luxttsPending.delete(msg.id);
39764
+ if (msg.type === "error")
39765
+ pending.reject(new Error(msg.error));
39766
+ else
39767
+ pending.resolve(line);
39768
+ }
39769
+ }
39770
+ } catch {
39771
+ }
39772
+ }
39773
+ });
39774
+ daemon.on("exit", () => {
39775
+ this._luxttsDaemon = null;
39776
+ for (const [, p] of this._luxttsPending) {
39777
+ p.reject(new Error("LuxTTS daemon exited"));
39778
+ }
39779
+ this._luxttsPending.clear();
39780
+ });
39781
+ daemon.on("error", () => {
39782
+ this._luxttsDaemon = null;
39783
+ resolve31(false);
39784
+ });
39785
+ setTimeout(() => {
39786
+ if (this._luxttsDaemon === daemon && !this._luxttsPending.has("__ready__")) {
39787
+ resolve31(false);
39788
+ }
39789
+ }, 6e4);
39790
+ });
39791
+ }
39792
+ /** Send a request to the LuxTTS daemon and await the response */
39793
+ luxttsRequest(req) {
39794
+ return new Promise((resolve31, reject) => {
39795
+ if (!this._luxttsDaemon || this._luxttsDaemon.killed) {
39796
+ reject(new Error("LuxTTS daemon not running"));
39797
+ return;
39798
+ }
39799
+ const id = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
39800
+ req.id = id;
39801
+ this._luxttsPending.set(id, { resolve: resolve31, reject });
39802
+ this._luxttsDaemon.stdin.write(JSON.stringify(req) + "\n");
39803
+ setTimeout(() => {
39804
+ if (this._luxttsPending.has(id)) {
39805
+ this._luxttsPending.delete(id);
39806
+ reject(new Error("LuxTTS synthesis timeout"));
39807
+ }
39808
+ }, 12e4);
39809
+ });
39810
+ }
39685
39811
  /**
39686
39812
  * Synthesize and play audio using LuxTTS voice cloning.
39687
39813
  * Speed is passed natively to LuxTTS. Pitch is post-processed via resampling.
@@ -39694,20 +39820,18 @@ if __name__ == '__main__':
39694
39820
  const cleaned = text.replace(/\*/g, "").trim();
39695
39821
  if (!cleaned)
39696
39822
  return;
39697
- const venvPy = luxttsVenvPy();
39698
- if (!existsSync34(venvPy))
39823
+ const ready = await this.ensureLuxttsDaemon();
39824
+ if (!ready)
39699
39825
  return;
39700
39826
  const wavPath = join43(tmpdir6(), `oa-luxtts-${Date.now()}.wav`);
39701
- const inferArgs = JSON.stringify({
39702
- action: "synthesize",
39703
- repo_path: luxttsRepoDir(),
39704
- text: cleaned,
39705
- clone_ref: this.luxttsCloneRef,
39706
- output_path: wavPath,
39707
- speed: speedFactor
39708
- });
39709
39827
  try {
39710
- execSync27(`${JSON.stringify(venvPy)} ${JSON.stringify(luxttsInferScript())} ${JSON.stringify(inferArgs)}`, { stdio: "pipe", timeout: 12e4, cwd: tmpdir6() });
39828
+ await this.luxttsRequest({
39829
+ action: "synthesize",
39830
+ text: cleaned,
39831
+ clone_ref: this.luxttsCloneRef,
39832
+ output_path: wavPath,
39833
+ speed: speedFactor
39834
+ });
39711
39835
  } catch {
39712
39836
  return;
39713
39837
  }
@@ -39770,20 +39894,18 @@ if __name__ == '__main__':
39770
39894
  const cleaned = text.replace(/\*/g, "").trim();
39771
39895
  if (!cleaned)
39772
39896
  return null;
39773
- const venvPy = luxttsVenvPy();
39774
- if (!existsSync34(venvPy))
39897
+ const ready = await this.ensureLuxttsDaemon();
39898
+ if (!ready)
39775
39899
  return null;
39776
39900
  const wavPath = join43(tmpdir6(), `oa-luxtts-buf-${Date.now()}.wav`);
39777
- const inferArgs = JSON.stringify({
39778
- action: "synthesize",
39779
- repo_path: luxttsRepoDir(),
39780
- text: cleaned,
39781
- clone_ref: this.luxttsCloneRef,
39782
- output_path: wavPath,
39783
- speed: 1
39784
- });
39785
39901
  try {
39786
- execSync27(`${JSON.stringify(venvPy)} ${JSON.stringify(luxttsInferScript())} ${JSON.stringify(inferArgs)}`, { stdio: "pipe", timeout: 12e4, cwd: tmpdir6() });
39902
+ await this.luxttsRequest({
39903
+ action: "synthesize",
39904
+ text: cleaned,
39905
+ clone_ref: this.luxttsCloneRef,
39906
+ output_path: wavPath,
39907
+ speed: 1
39908
+ });
39787
39909
  } catch {
39788
39910
  return null;
39789
39911
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.103.80",
3
+ "version": "0.103.82",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",