open-agents-ai 0.103.79 → 0.103.81
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 +245 -84
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19134,7 +19134,8 @@ If you're stuck, try a completely different approach. Do NOT repeat what failed
|
|
|
19134
19134
|
if (!choice)
|
|
19135
19135
|
break;
|
|
19136
19136
|
const msg = choice.message;
|
|
19137
|
-
const
|
|
19137
|
+
const isThinkOnly = response._thinkOnly === true;
|
|
19138
|
+
const isEmptyResponse = !isThinkOnly && (!msg.content || !msg.content.trim()) && (!msg.toolCalls || msg.toolCalls.length === 0);
|
|
19138
19139
|
if (isEmptyResponse) {
|
|
19139
19140
|
consecutiveEmptyResponses++;
|
|
19140
19141
|
if (consecutiveEmptyResponses >= 2) {
|
|
@@ -20970,6 +20971,8 @@ ${transcript}`
|
|
|
20970
20971
|
}
|
|
20971
20972
|
this.emit({ type: "stream_end", content, turn, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
20972
20973
|
const cleanContent = content.replace(/<think>[\s\S]*?<\/think>/g, "").trim();
|
|
20974
|
+
const hadThinking = content.includes("<think>") && content.includes("</think>");
|
|
20975
|
+
const thinkOnlyResponse = hadThinking && !cleanContent;
|
|
20973
20976
|
const toolCalls = toolCallAccumulators.size > 0 ? Array.from(toolCallAccumulators.values()).map((tc) => {
|
|
20974
20977
|
let args;
|
|
20975
20978
|
try {
|
|
@@ -20980,10 +20983,20 @@ ${transcript}`
|
|
|
20980
20983
|
}
|
|
20981
20984
|
return { id: tc.id, name: tc.name, arguments: args };
|
|
20982
20985
|
}) : void 0;
|
|
20983
|
-
|
|
20984
|
-
choices: [{
|
|
20986
|
+
const resp = {
|
|
20987
|
+
choices: [{
|
|
20988
|
+
message: {
|
|
20989
|
+
// For think-only responses, include a marker so the model knows it already
|
|
20990
|
+
// thought about this (prevents infinite think loops without visible output)
|
|
20991
|
+
content: thinkOnlyResponse ? "[Your previous response was internal reasoning only. Now respond with visible text or a tool call.]" : cleanContent || null,
|
|
20992
|
+
toolCalls
|
|
20993
|
+
}
|
|
20994
|
+
}],
|
|
20985
20995
|
usage: streamUsage
|
|
20986
20996
|
};
|
|
20997
|
+
if (thinkOnlyResponse)
|
|
20998
|
+
resp._thinkOnly = true;
|
|
20999
|
+
return resp;
|
|
20987
21000
|
}
|
|
20988
21001
|
};
|
|
20989
21002
|
OllamaAgenticBackend = class {
|
|
@@ -38944,6 +38957,19 @@ var init_voice = __esm({
|
|
|
38944
38957
|
this.config = null;
|
|
38945
38958
|
this.mlxActive = false;
|
|
38946
38959
|
this.luxttsActive = false;
|
|
38960
|
+
if (this._luxttsDaemon && !this._luxttsDaemon.killed) {
|
|
38961
|
+
try {
|
|
38962
|
+
this._luxttsDaemon.stdin.write('{"action":"quit"}\n');
|
|
38963
|
+
} catch {
|
|
38964
|
+
}
|
|
38965
|
+
setTimeout(() => {
|
|
38966
|
+
try {
|
|
38967
|
+
this._luxttsDaemon?.kill();
|
|
38968
|
+
} catch {
|
|
38969
|
+
}
|
|
38970
|
+
}, 2e3);
|
|
38971
|
+
}
|
|
38972
|
+
this._luxttsDaemon = null;
|
|
38947
38973
|
}
|
|
38948
38974
|
// -------------------------------------------------------------------------
|
|
38949
38975
|
// Text chunking for long TTS input
|
|
@@ -39471,6 +39497,23 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
|
39471
39497
|
if (existsSync34(venvPy)) {
|
|
39472
39498
|
try {
|
|
39473
39499
|
execSync27(`${venvPy} -c "import sys; sys.path.insert(0, '${luxttsRepoDir()}'); from zipvoice.luxvoice import LuxTTS; print('ok')"`, { stdio: "pipe", timeout: 3e4 });
|
|
39500
|
+
let hasCudaSys = false;
|
|
39501
|
+
try {
|
|
39502
|
+
execSync27("nvidia-smi", { stdio: "pipe", timeout: 5e3 });
|
|
39503
|
+
hasCudaSys = true;
|
|
39504
|
+
} catch {
|
|
39505
|
+
}
|
|
39506
|
+
if (hasCudaSys) {
|
|
39507
|
+
try {
|
|
39508
|
+
const torchCheck = execSync27(`${venvPy} -c "import torch; print('cuda' if torch.cuda.is_available() else 'cpu')"`, { stdio: "pipe", timeout: 15e3 }).toString().trim();
|
|
39509
|
+
if (torchCheck === "cpu") {
|
|
39510
|
+
renderWarning("GPU detected but PyTorch is CPU-only. Reinstalling with CUDA support...");
|
|
39511
|
+
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet --force-reinstall torch torchaudio --index-url https://download.pytorch.org/whl/cu124`, { stdio: "pipe", timeout: 6e5 });
|
|
39512
|
+
renderInfo("PyTorch reinstalled with CUDA GPU support.");
|
|
39513
|
+
}
|
|
39514
|
+
} catch {
|
|
39515
|
+
}
|
|
39516
|
+
}
|
|
39474
39517
|
this.writeLuxttsInferScript();
|
|
39475
39518
|
this.autoDetectCloneRef();
|
|
39476
39519
|
return;
|
|
@@ -39490,14 +39533,33 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
|
39490
39533
|
throw new Error(`Failed to create venv: ${err instanceof Error ? err.message : String(err)}`);
|
|
39491
39534
|
}
|
|
39492
39535
|
}
|
|
39493
|
-
|
|
39536
|
+
let hasCuda = false;
|
|
39494
39537
|
try {
|
|
39495
|
-
execSync27(
|
|
39496
|
-
|
|
39538
|
+
execSync27("nvidia-smi", { stdio: "pipe", timeout: 5e3 });
|
|
39539
|
+
hasCuda = true;
|
|
39540
|
+
} catch {
|
|
39541
|
+
}
|
|
39542
|
+
if (hasCuda) {
|
|
39543
|
+
renderInfo(" Installing PyTorch (CUDA GPU)...");
|
|
39497
39544
|
try {
|
|
39498
|
-
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet torch torchaudio`, { stdio: "pipe", timeout: 6e5 });
|
|
39499
|
-
} catch
|
|
39500
|
-
|
|
39545
|
+
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet torch torchaudio --index-url https://download.pytorch.org/whl/cu124`, { stdio: "pipe", timeout: 6e5 });
|
|
39546
|
+
} catch {
|
|
39547
|
+
try {
|
|
39548
|
+
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet torch torchaudio`, { stdio: "pipe", timeout: 6e5 });
|
|
39549
|
+
} catch (err2) {
|
|
39550
|
+
throw new Error(`Failed to install PyTorch (CUDA): ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
39551
|
+
}
|
|
39552
|
+
}
|
|
39553
|
+
} else {
|
|
39554
|
+
renderInfo(" Installing PyTorch (CPU \u2014 no NVIDIA GPU detected)...");
|
|
39555
|
+
try {
|
|
39556
|
+
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet torch torchaudio --index-url https://download.pytorch.org/whl/cpu`, { stdio: "pipe", timeout: 6e5 });
|
|
39557
|
+
} catch {
|
|
39558
|
+
try {
|
|
39559
|
+
execSync27(`${JSON.stringify(venvPy)} -m pip install --quiet torch torchaudio`, { stdio: "pipe", timeout: 6e5 });
|
|
39560
|
+
} catch (err2) {
|
|
39561
|
+
throw new Error(`Failed to install PyTorch: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
39562
|
+
}
|
|
39501
39563
|
}
|
|
39502
39564
|
}
|
|
39503
39565
|
const repoDir = luxttsRepoDir();
|
|
@@ -39561,70 +39623,95 @@ Error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
|
39561
39623
|
}
|
|
39562
39624
|
}
|
|
39563
39625
|
}
|
|
39564
|
-
/**
|
|
39626
|
+
/** Persistent LuxTTS daemon process — keeps model warm in GPU memory */
|
|
39627
|
+
_luxttsDaemon = null;
|
|
39628
|
+
_luxttsPending = /* @__PURE__ */ new Map();
|
|
39629
|
+
_luxttsBuffer = "";
|
|
39630
|
+
/** Write the LuxTTS persistent daemon Python script */
|
|
39565
39631
|
writeLuxttsInferScript() {
|
|
39566
39632
|
const script = `#!/usr/bin/env python3
|
|
39567
|
-
"""LuxTTS
|
|
39568
|
-
|
|
39633
|
+
"""LuxTTS persistent daemon for open-agents voice engine.
|
|
39634
|
+
Keeps model warm in GPU memory. Reads JSON requests from stdin, writes JSON responses to stdout.
|
|
39569
39635
|
"""
|
|
39570
|
-
import sys, json, os, wave,
|
|
39636
|
+
import sys, json, os, wave, signal
|
|
39571
39637
|
import numpy as np
|
|
39572
39638
|
|
|
39573
39639
|
def main():
|
|
39574
|
-
|
|
39575
|
-
action = args.get('action', 'synthesize')
|
|
39576
|
-
|
|
39577
|
-
repo_path = args.get('repo_path', '')
|
|
39640
|
+
repo_path = os.environ.get('LUXTTS_REPO_PATH', '')
|
|
39578
39641
|
if repo_path:
|
|
39579
39642
|
sys.path.insert(0, repo_path)
|
|
39580
39643
|
|
|
39581
|
-
|
|
39644
|
+
import torch
|
|
39645
|
+
from zipvoice.luxvoice import LuxTTS
|
|
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
|
+
# Load model ONCE \u2014 stays warm in GPU/CPU memory
|
|
39656
|
+
tts = LuxTTS(model_path='YatharthS/LuxTTS', device=device, threads=4)
|
|
39657
|
+
sys.stdout.write(json.dumps({'type': 'ready', 'device': device}) + '\\n')
|
|
39658
|
+
sys.stdout.flush()
|
|
39659
|
+
|
|
39660
|
+
# Cache encoded prompts by clone_ref path (avoid re-encoding same voice)
|
|
39661
|
+
prompt_cache = {}
|
|
39662
|
+
|
|
39663
|
+
for line in sys.stdin:
|
|
39664
|
+
line = line.strip()
|
|
39665
|
+
if not line:
|
|
39666
|
+
continue
|
|
39582
39667
|
try:
|
|
39583
|
-
|
|
39584
|
-
print(json.dumps({'ok': True}))
|
|
39668
|
+
req = json.loads(line)
|
|
39585
39669
|
except Exception as e:
|
|
39586
|
-
|
|
39587
|
-
|
|
39588
|
-
|
|
39589
|
-
|
|
39590
|
-
|
|
39591
|
-
|
|
39592
|
-
|
|
39593
|
-
|
|
39594
|
-
|
|
39595
|
-
|
|
39596
|
-
|
|
39597
|
-
|
|
39598
|
-
|
|
39599
|
-
|
|
39600
|
-
|
|
39601
|
-
|
|
39602
|
-
|
|
39603
|
-
|
|
39604
|
-
|
|
39605
|
-
|
|
39606
|
-
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
|
|
39610
|
-
|
|
39611
|
-
|
|
39612
|
-
|
|
39613
|
-
|
|
39614
|
-
|
|
39615
|
-
|
|
39616
|
-
|
|
39617
|
-
|
|
39618
|
-
|
|
39619
|
-
|
|
39620
|
-
|
|
39621
|
-
|
|
39622
|
-
|
|
39623
|
-
|
|
39624
|
-
|
|
39625
|
-
|
|
39626
|
-
|
|
39627
|
-
|
|
39670
|
+
sys.stdout.write(json.dumps({'type': 'error', 'id': '', 'error': str(e)}) + '\\n')
|
|
39671
|
+
sys.stdout.flush()
|
|
39672
|
+
continue
|
|
39673
|
+
|
|
39674
|
+
req_id = req.get('id', '')
|
|
39675
|
+
|
|
39676
|
+
if req.get('action') == 'check':
|
|
39677
|
+
sys.stdout.write(json.dumps({'type': 'result', 'id': req_id, 'ok': True}) + '\\n')
|
|
39678
|
+
sys.stdout.flush()
|
|
39679
|
+
continue
|
|
39680
|
+
|
|
39681
|
+
if req.get('action') == 'quit':
|
|
39682
|
+
break
|
|
39683
|
+
|
|
39684
|
+
if req.get('action') == 'synthesize':
|
|
39685
|
+
try:
|
|
39686
|
+
text = req['text']
|
|
39687
|
+
clone_ref = req['clone_ref']
|
|
39688
|
+
output_path = req['output_path']
|
|
39689
|
+
speed = req.get('speed', 1.0)
|
|
39690
|
+
|
|
39691
|
+
# Use cached prompt encoding if available
|
|
39692
|
+
if clone_ref not in prompt_cache:
|
|
39693
|
+
prompt_cache[clone_ref] = tts.encode_prompt(clone_ref, duration=5, rms=0.001)
|
|
39694
|
+
encoded = prompt_cache[clone_ref]
|
|
39695
|
+
|
|
39696
|
+
wav_tensor = tts.generate_speech(text, encoded, num_steps=4, guidance_scale=3.0, t_shift=0.5, speed=speed)
|
|
39697
|
+
wav_np = wav_tensor.cpu().numpy().squeeze()
|
|
39698
|
+
|
|
39699
|
+
int16_data = (np.clip(wav_np, -1.0, 1.0) * 32767).astype(np.int16)
|
|
39700
|
+
with wave.open(output_path, 'wb') as wf:
|
|
39701
|
+
wf.setnchannels(1)
|
|
39702
|
+
wf.setsampwidth(2)
|
|
39703
|
+
wf.setframerate(48000)
|
|
39704
|
+
wf.writeframes(int16_data.tobytes())
|
|
39705
|
+
|
|
39706
|
+
sys.stdout.write(json.dumps({
|
|
39707
|
+
'type': 'result', 'id': req_id, 'ok': True,
|
|
39708
|
+
'path': output_path, 'sample_rate': 48000,
|
|
39709
|
+
'device': device, 'samples': len(int16_data),
|
|
39710
|
+
}) + '\\n')
|
|
39711
|
+
sys.stdout.flush()
|
|
39712
|
+
except Exception as e:
|
|
39713
|
+
sys.stdout.write(json.dumps({'type': 'error', 'id': req_id, 'error': str(e)}) + '\\n')
|
|
39714
|
+
sys.stdout.flush()
|
|
39628
39715
|
|
|
39629
39716
|
if __name__ == '__main__':
|
|
39630
39717
|
main()
|
|
@@ -39633,6 +39720,84 @@ if __name__ == '__main__':
|
|
|
39633
39720
|
mkdirSync14(voiceDir(), { recursive: true });
|
|
39634
39721
|
writeFileSync14(scriptPath2, script);
|
|
39635
39722
|
}
|
|
39723
|
+
/** Ensure the LuxTTS daemon is running, spawn if needed */
|
|
39724
|
+
async ensureLuxttsDaemon() {
|
|
39725
|
+
if (this._luxttsDaemon && !this._luxttsDaemon.killed)
|
|
39726
|
+
return true;
|
|
39727
|
+
const venvPy = luxttsVenvPy();
|
|
39728
|
+
if (!existsSync34(venvPy))
|
|
39729
|
+
return false;
|
|
39730
|
+
return new Promise((resolve31) => {
|
|
39731
|
+
const env = { ...process.env, LUXTTS_REPO_PATH: luxttsRepoDir() };
|
|
39732
|
+
const daemon = nodeSpawn(venvPy, [luxttsInferScript()], {
|
|
39733
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
39734
|
+
cwd: tmpdir6(),
|
|
39735
|
+
env
|
|
39736
|
+
});
|
|
39737
|
+
this._luxttsDaemon = daemon;
|
|
39738
|
+
this._luxttsBuffer = "";
|
|
39739
|
+
daemon.stdout.on("data", (chunk) => {
|
|
39740
|
+
this._luxttsBuffer += chunk.toString();
|
|
39741
|
+
const lines = this._luxttsBuffer.split("\n");
|
|
39742
|
+
this._luxttsBuffer = lines.pop();
|
|
39743
|
+
for (const line of lines) {
|
|
39744
|
+
if (!line.trim())
|
|
39745
|
+
continue;
|
|
39746
|
+
try {
|
|
39747
|
+
const msg = JSON.parse(line);
|
|
39748
|
+
if (msg.type === "ready") {
|
|
39749
|
+
resolve31(true);
|
|
39750
|
+
} else if (msg.type === "result" || msg.type === "error") {
|
|
39751
|
+
const pending = this._luxttsPending.get(msg.id);
|
|
39752
|
+
if (pending) {
|
|
39753
|
+
this._luxttsPending.delete(msg.id);
|
|
39754
|
+
if (msg.type === "error")
|
|
39755
|
+
pending.reject(new Error(msg.error));
|
|
39756
|
+
else
|
|
39757
|
+
pending.resolve(line);
|
|
39758
|
+
}
|
|
39759
|
+
}
|
|
39760
|
+
} catch {
|
|
39761
|
+
}
|
|
39762
|
+
}
|
|
39763
|
+
});
|
|
39764
|
+
daemon.on("exit", () => {
|
|
39765
|
+
this._luxttsDaemon = null;
|
|
39766
|
+
for (const [, p] of this._luxttsPending) {
|
|
39767
|
+
p.reject(new Error("LuxTTS daemon exited"));
|
|
39768
|
+
}
|
|
39769
|
+
this._luxttsPending.clear();
|
|
39770
|
+
});
|
|
39771
|
+
daemon.on("error", () => {
|
|
39772
|
+
this._luxttsDaemon = null;
|
|
39773
|
+
resolve31(false);
|
|
39774
|
+
});
|
|
39775
|
+
setTimeout(() => {
|
|
39776
|
+
if (this._luxttsDaemon === daemon && !this._luxttsPending.has("__ready__")) {
|
|
39777
|
+
resolve31(false);
|
|
39778
|
+
}
|
|
39779
|
+
}, 6e4);
|
|
39780
|
+
});
|
|
39781
|
+
}
|
|
39782
|
+
/** Send a request to the LuxTTS daemon and await the response */
|
|
39783
|
+
luxttsRequest(req) {
|
|
39784
|
+
return new Promise((resolve31, reject) => {
|
|
39785
|
+
if (!this._luxttsDaemon || this._luxttsDaemon.killed) {
|
|
39786
|
+
reject(new Error("LuxTTS daemon not running"));
|
|
39787
|
+
return;
|
|
39788
|
+
}
|
|
39789
|
+
const id = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
39790
|
+
req.id = id;
|
|
39791
|
+
this._luxttsPending.set(id, { resolve: resolve31, reject });
|
|
39792
|
+
this._luxttsDaemon.stdin.write(JSON.stringify(req) + "\n");
|
|
39793
|
+
setTimeout(() => {
|
|
39794
|
+
if (this._luxttsPending.has(id)) {
|
|
39795
|
+
this._luxttsPending.delete(id);
|
|
39796
|
+
reject(new Error("LuxTTS synthesis timeout"));
|
|
39797
|
+
}
|
|
39798
|
+
}, 12e4);
|
|
39799
|
+
});
|
|
39800
|
+
}
|
|
39636
39801
|
/**
|
|
39637
39802
|
* Synthesize and play audio using LuxTTS voice cloning.
|
|
39638
39803
|
* Speed is passed natively to LuxTTS. Pitch is post-processed via resampling.
|
|
@@ -39645,20 +39810,18 @@ if __name__ == '__main__':
|
|
|
39645
39810
|
const cleaned = text.replace(/\*/g, "").trim();
|
|
39646
39811
|
if (!cleaned)
|
|
39647
39812
|
return;
|
|
39648
|
-
const
|
|
39649
|
-
if (!
|
|
39813
|
+
const ready = await this.ensureLuxttsDaemon();
|
|
39814
|
+
if (!ready)
|
|
39650
39815
|
return;
|
|
39651
39816
|
const wavPath = join43(tmpdir6(), `oa-luxtts-${Date.now()}.wav`);
|
|
39652
|
-
const inferArgs = JSON.stringify({
|
|
39653
|
-
action: "synthesize",
|
|
39654
|
-
repo_path: luxttsRepoDir(),
|
|
39655
|
-
text: cleaned,
|
|
39656
|
-
clone_ref: this.luxttsCloneRef,
|
|
39657
|
-
output_path: wavPath,
|
|
39658
|
-
speed: speedFactor
|
|
39659
|
-
});
|
|
39660
39817
|
try {
|
|
39661
|
-
|
|
39818
|
+
await this.luxttsRequest({
|
|
39819
|
+
action: "synthesize",
|
|
39820
|
+
text: cleaned,
|
|
39821
|
+
clone_ref: this.luxttsCloneRef,
|
|
39822
|
+
output_path: wavPath,
|
|
39823
|
+
speed: speedFactor
|
|
39824
|
+
});
|
|
39662
39825
|
} catch {
|
|
39663
39826
|
return;
|
|
39664
39827
|
}
|
|
@@ -39721,20 +39884,18 @@ if __name__ == '__main__':
|
|
|
39721
39884
|
const cleaned = text.replace(/\*/g, "").trim();
|
|
39722
39885
|
if (!cleaned)
|
|
39723
39886
|
return null;
|
|
39724
|
-
const
|
|
39725
|
-
if (!
|
|
39887
|
+
const ready = await this.ensureLuxttsDaemon();
|
|
39888
|
+
if (!ready)
|
|
39726
39889
|
return null;
|
|
39727
39890
|
const wavPath = join43(tmpdir6(), `oa-luxtts-buf-${Date.now()}.wav`);
|
|
39728
|
-
const inferArgs = JSON.stringify({
|
|
39729
|
-
action: "synthesize",
|
|
39730
|
-
repo_path: luxttsRepoDir(),
|
|
39731
|
-
text: cleaned,
|
|
39732
|
-
clone_ref: this.luxttsCloneRef,
|
|
39733
|
-
output_path: wavPath,
|
|
39734
|
-
speed: 1
|
|
39735
|
-
});
|
|
39736
39891
|
try {
|
|
39737
|
-
|
|
39892
|
+
await this.luxttsRequest({
|
|
39893
|
+
action: "synthesize",
|
|
39894
|
+
text: cleaned,
|
|
39895
|
+
clone_ref: this.luxttsCloneRef,
|
|
39896
|
+
output_path: wavPath,
|
|
39897
|
+
speed: 1
|
|
39898
|
+
});
|
|
39738
39899
|
} catch {
|
|
39739
39900
|
return null;
|
|
39740
39901
|
}
|
package/package.json
CHANGED