careervivid 2.1.13 โ 2.1.16
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/commands/agent/repl.d.ts.map +1 -1
- package/dist/commands/agent/repl.js +50 -8
- package/dist/lib/tts.d.ts +13 -12
- package/dist/lib/tts.d.ts.map +1 -1
- package/dist/lib/tts.js +84 -39
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../../../src/commands/agent/repl.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAS7G,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,MAAM,GAAG,IAAW,QAwBtF;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,WAAW,GAAG,sBAAsB,GAAG,IAAI,EACnD,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9K,gBAAgB,EAAE,WAAW,EAC7B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,iBAAiB,EAAE,MAAM,EACzB,KAAK,EAAE,GAAG,EAAE,GACX,OAAO,CAAC,IAAI,CAAC,CA6uBf"}
|
|
@@ -4,10 +4,10 @@ import ora from "ora";
|
|
|
4
4
|
import { isSafeCommand } from "../../agent/tools/coding.js";
|
|
5
5
|
import { CareerVividProxyEngine } from "../../agent/CareerVividProxyEngine.js";
|
|
6
6
|
import { CV_MODELS } from "./configurator.js";
|
|
7
|
-
import { loadConfig,
|
|
7
|
+
import { loadConfig, getProviderKey, setProviderKey } from "../../config.js";
|
|
8
8
|
import { auditLog, writeSessionSummary, SESSION_ID } from "../../agent/agentAuditLog.js";
|
|
9
9
|
import { runShellEscape } from "../../lib/shell.js";
|
|
10
|
-
import { isVoiceEnabled, setVoiceEnabled, setLastResponse, getLastResponse, speakText, stopPlayback } from "../../lib/tts.js";
|
|
10
|
+
import { isVoiceEnabled, setVoiceEnabled, setLastResponse, getLastResponse, speakText, stopPlayback, getCurrentVoice, setCurrentVoice, getCurrentTtsModel, setCurrentTtsModel, AVAILABLE_VOICES, AVAILABLE_TTS_MODELS } from "../../lib/tts.js";
|
|
11
11
|
const { prompt } = pkg;
|
|
12
12
|
export function printCreditStatus(remaining, limit = null) {
|
|
13
13
|
if (remaining === null)
|
|
@@ -198,16 +198,60 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
|
|
|
198
198
|
if (cmd === "voice") {
|
|
199
199
|
if (arg === "on") {
|
|
200
200
|
setVoiceEnabled(true);
|
|
201
|
-
console.log(chalk.green(
|
|
201
|
+
console.log(chalk.green(`\n ๐ Voice enabled (${getCurrentVoice()} ยท ${getCurrentTtsModel()}).\n`));
|
|
202
202
|
}
|
|
203
203
|
else if (arg === "off") {
|
|
204
204
|
setVoiceEnabled(false);
|
|
205
205
|
stopPlayback();
|
|
206
206
|
console.log(chalk.yellow("\n ๐ Voice disabled.\n"));
|
|
207
207
|
}
|
|
208
|
+
else if (arg === "list-voices" || arg === "voices") {
|
|
209
|
+
console.log(chalk.cyan("\n Available voices:"));
|
|
210
|
+
for (const v of AVAILABLE_VOICES) {
|
|
211
|
+
const active = v === getCurrentVoice() ? chalk.green(" โ active") : "";
|
|
212
|
+
console.log(chalk.dim(` ${v}${active}`));
|
|
213
|
+
}
|
|
214
|
+
console.log(chalk.dim("\n Usage: /voice set-voice Puck\n"));
|
|
215
|
+
}
|
|
216
|
+
else if (arg.startsWith("set-voice ")) {
|
|
217
|
+
const name = arg.slice("set-voice ".length).trim();
|
|
218
|
+
const match = AVAILABLE_VOICES.find(v => v.toLowerCase() === name.toLowerCase());
|
|
219
|
+
if (!match) {
|
|
220
|
+
console.log(chalk.red(`\n Unknown voice: "${name}". Run /voice list-voices to see options.\n`));
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
setCurrentVoice(match);
|
|
224
|
+
console.log(chalk.green(`\n ๐ต Voice set to ${chalk.bold(match)}.\n`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (arg === "list-models" || arg === "models") {
|
|
228
|
+
console.log(chalk.cyan("\n Available TTS models:"));
|
|
229
|
+
for (const m of AVAILABLE_TTS_MODELS) {
|
|
230
|
+
const active = m === getCurrentTtsModel() ? chalk.green(" โ active") : "";
|
|
231
|
+
console.log(chalk.dim(` ${m}${active}`));
|
|
232
|
+
}
|
|
233
|
+
console.log(chalk.dim("\n Usage: /voice set-model gemini-2.5-pro-preview-tts\n"));
|
|
234
|
+
}
|
|
235
|
+
else if (arg.startsWith("set-model ")) {
|
|
236
|
+
const name = arg.slice("set-model ".length).trim();
|
|
237
|
+
const match = AVAILABLE_TTS_MODELS.find(m => m === name);
|
|
238
|
+
if (!match) {
|
|
239
|
+
console.log(chalk.red(`\n Unknown model: "${name}". Run /voice list-models to see options.\n`));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
setCurrentTtsModel(match);
|
|
243
|
+
console.log(chalk.green(`\n โ๏ธ TTS model set to ${chalk.bold(match)}.\n`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
208
246
|
else {
|
|
209
247
|
const status = isVoiceEnabled() ? chalk.green("on") : chalk.yellow("off");
|
|
210
|
-
console.log(chalk.dim(`\n Voice is
|
|
248
|
+
console.log(chalk.dim(`\n Voice is ${status} ยท Voice: ${chalk.white(getCurrentVoice())} ยท Model: ${chalk.white(getCurrentTtsModel())}`));
|
|
249
|
+
console.log(chalk.dim("\n Commands:"));
|
|
250
|
+
console.log(chalk.dim(" /voice on | off"));
|
|
251
|
+
console.log(chalk.dim(" /voice set-voice <name> e.g. /voice set-voice Puck"));
|
|
252
|
+
console.log(chalk.dim(" /voice list-voices"));
|
|
253
|
+
console.log(chalk.dim(" /voice set-model <name> e.g. /voice set-model gemini-2.5-pro-preview-tts"));
|
|
254
|
+
console.log(chalk.dim(" /voice list-models\n"));
|
|
211
255
|
}
|
|
212
256
|
return ask();
|
|
213
257
|
}
|
|
@@ -217,8 +261,7 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
|
|
|
217
261
|
console.log(chalk.dim("\n Nothing to speak yet. Ask the agent something first.\n"));
|
|
218
262
|
}
|
|
219
263
|
else {
|
|
220
|
-
|
|
221
|
-
speakText(last, geminiKey ?? undefined).catch(() => { });
|
|
264
|
+
speakText(last).catch(() => { });
|
|
222
265
|
console.log(chalk.dim("\n ๐ Speaking last response...\n"));
|
|
223
266
|
}
|
|
224
267
|
return ask();
|
|
@@ -525,8 +568,7 @@ export async function askLoop(engine, options, selectedProvider, selectedModel,
|
|
|
525
568
|
if (responseAccumulator) {
|
|
526
569
|
setLastResponse(responseAccumulator);
|
|
527
570
|
if (isVoiceEnabled()) {
|
|
528
|
-
|
|
529
|
-
speakText(responseAccumulator, geminiKey ?? undefined).catch(() => { });
|
|
571
|
+
speakText(responseAccumulator).catch(() => { });
|
|
530
572
|
}
|
|
531
573
|
}
|
|
532
574
|
// โโ Clean turn separator after every AI reply โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
package/dist/lib/tts.d.ts
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* tts.ts โ Text-to-Speech engine for the CareerVivid REPL
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the main thread.
|
|
4
|
+
* Authenticates using the user's CareerVivid API key (cv_live_...) to fetch
|
|
5
|
+
* a short-lived Gemini key from the backend โ exactly like `cv interview`.
|
|
6
|
+
* No separate GEMINI_API_KEY required.
|
|
8
7
|
*
|
|
9
8
|
* Toggle: /voice on | /voice off
|
|
10
|
-
*
|
|
9
|
+
* Replay: /speak
|
|
11
10
|
*/
|
|
11
|
+
export declare const AVAILABLE_VOICES: readonly ["Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Aoede", "Orbit", "Stellar", "Leda", "Orus"];
|
|
12
|
+
export declare const AVAILABLE_TTS_MODELS: readonly ["gemini-2.5-flash-preview-tts", "gemini-2.5-pro-preview-tts"];
|
|
12
13
|
export declare function isVoiceEnabled(): boolean;
|
|
13
14
|
export declare function setVoiceEnabled(on: boolean): void;
|
|
14
15
|
export declare function setLastResponse(text: string): void;
|
|
15
16
|
export declare function getLastResponse(): string;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
export declare function getCurrentVoice(): string;
|
|
18
|
+
export declare function setCurrentVoice(v: string): void;
|
|
19
|
+
export declare function getCurrentTtsModel(): string;
|
|
20
|
+
export declare function setCurrentTtsModel(m: string): void;
|
|
19
21
|
export declare function stopPlayback(): void;
|
|
20
22
|
/**
|
|
21
|
-
* Synthesizes `text` via Gemini TTS
|
|
22
|
-
*
|
|
23
|
-
* REPL is never disrupted by TTS failures.
|
|
23
|
+
* Synthesizes `text` via Gemini TTS using the CareerVivid API key for auth.
|
|
24
|
+
* Non-blocking โ errors are silently swallowed so the REPL is never disrupted.
|
|
24
25
|
*/
|
|
25
|
-
export declare function speakText(text: string,
|
|
26
|
+
export declare function speakText(text: string, _unusedKey?: string): Promise<void>;
|
|
26
27
|
//# sourceMappingURL=tts.d.ts.map
|
package/dist/lib/tts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../src/lib/tts.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../../src/lib/tts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH,eAAO,MAAM,gBAAgB,sGAWnB,CAAC;AAEX,eAAO,MAAM,oBAAoB,yEAGvB,CAAC;AAYX,wBAAgB,cAAc,YAA2B;AACzD,wBAAgB,eAAe,CAAC,EAAE,EAAE,OAAO,QAAwB;AACnE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,QAA0B;AACtE,wBAAgB,eAAe,WAA2B;AAC1D,wBAAgB,eAAe,WAA2B;AAC1D,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,QAAuB;AAChE,wBAAgB,kBAAkB,WAA8B;AAChE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,QAA0B;AA8BtE,wBAAgB,YAAY,SAK3B;AA2DD;;;GAGG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDhF"}
|
package/dist/lib/tts.js
CHANGED
|
@@ -1,41 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* tts.ts โ Text-to-Speech engine for the CareerVivid REPL
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* the main thread.
|
|
4
|
+
* Authenticates using the user's CareerVivid API key (cv_live_...) to fetch
|
|
5
|
+
* a short-lived Gemini key from the backend โ exactly like `cv interview`.
|
|
6
|
+
* No separate GEMINI_API_KEY required.
|
|
8
7
|
*
|
|
9
8
|
* Toggle: /voice on | /voice off
|
|
10
|
-
*
|
|
9
|
+
* Replay: /speak
|
|
11
10
|
*/
|
|
12
11
|
import { writeFileSync, unlinkSync } from "fs";
|
|
13
12
|
import { spawn } from "child_process";
|
|
14
13
|
import { tmpdir } from "os";
|
|
15
14
|
import { join } from "path";
|
|
16
15
|
import { GoogleGenAI, Modality } from "@google/genai";
|
|
16
|
+
import { getApiKey } from "../config.js";
|
|
17
|
+
// โโ Backend endpoint (same as interview token vend) โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
18
|
+
const TTS_TOKEN_URL = process.env.CV_FUNCTIONS_URL
|
|
19
|
+
? `${process.env.CV_FUNCTIONS_URL}/cliGetInterviewToken`
|
|
20
|
+
: "https://us-west1-jastalk-firebase.cloudfunctions.net/cliGetInterviewToken";
|
|
21
|
+
// โโ Available options โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
22
|
+
export const AVAILABLE_VOICES = [
|
|
23
|
+
"Zephyr", // Bright, energetic
|
|
24
|
+
"Puck", // Upbeat, playful
|
|
25
|
+
"Charon", // Informative, measured
|
|
26
|
+
"Kore", // Firm, confident
|
|
27
|
+
"Fenrir", // Excitable, dynamic
|
|
28
|
+
"Aoede", // Breezy, easy-going
|
|
29
|
+
"Orbit", // Friendly, relaxed
|
|
30
|
+
"Stellar", // Smooth, polished
|
|
31
|
+
"Leda", // Warm, natural
|
|
32
|
+
"Orus", // Confident, authoritative
|
|
33
|
+
];
|
|
34
|
+
export const AVAILABLE_TTS_MODELS = [
|
|
35
|
+
"gemini-2.5-flash-preview-tts", // Fast, efficient (default)
|
|
36
|
+
"gemini-2.5-pro-preview-tts", // Higher quality, slower
|
|
37
|
+
];
|
|
17
38
|
// โโ State โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
18
39
|
let voiceEnabled = false;
|
|
19
40
|
let lastResponse = "";
|
|
20
41
|
let playbackProcess = null;
|
|
42
|
+
let currentVoice = "Zephyr";
|
|
43
|
+
let currentTtsModel = "gemini-2.5-flash-preview-tts";
|
|
44
|
+
// Cache the Gemini key for the session so we don't hit the endpoint every turn
|
|
45
|
+
let cachedGeminiKey = null;
|
|
21
46
|
export function isVoiceEnabled() { return voiceEnabled; }
|
|
22
47
|
export function setVoiceEnabled(on) { voiceEnabled = on; }
|
|
23
48
|
export function setLastResponse(text) { lastResponse = text; }
|
|
24
49
|
export function getLastResponse() { return lastResponse; }
|
|
50
|
+
export function getCurrentVoice() { return currentVoice; }
|
|
51
|
+
export function setCurrentVoice(v) { currentVoice = v; }
|
|
52
|
+
export function getCurrentTtsModel() { return currentTtsModel; }
|
|
53
|
+
export function setCurrentTtsModel(m) { currentTtsModel = m; }
|
|
54
|
+
// โโ Gemini key via CV API key โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
55
|
+
async function fetchGeminiKey() {
|
|
56
|
+
if (cachedGeminiKey)
|
|
57
|
+
return cachedGeminiKey;
|
|
58
|
+
const apiKey = getApiKey();
|
|
59
|
+
if (!apiKey)
|
|
60
|
+
return null;
|
|
61
|
+
try {
|
|
62
|
+
const res = await fetch(TTS_TOKEN_URL, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({ apiKey, role: "tts" }),
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok)
|
|
68
|
+
return null;
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
if (data?.geminiKey) {
|
|
71
|
+
cachedGeminiKey = data.geminiKey;
|
|
72
|
+
return cachedGeminiKey;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Network error โ fall through
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
25
80
|
// โโ Audio Playback โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
26
|
-
/**
|
|
27
|
-
* Stops any currently playing audio immediately.
|
|
28
|
-
*/
|
|
29
81
|
export function stopPlayback() {
|
|
30
82
|
if (playbackProcess && !playbackProcess.killed) {
|
|
31
83
|
playbackProcess.kill("SIGKILL");
|
|
32
84
|
playbackProcess = null;
|
|
33
85
|
}
|
|
34
86
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Plays a WAV buffer using the OS audio player (non-blocking).
|
|
37
|
-
* afplay on macOS, aplay on Linux, powershell on Windows.
|
|
38
|
-
*/
|
|
39
87
|
function playWav(wavBuffer) {
|
|
40
88
|
const tmpFile = join(tmpdir(), `cv-tts-${Date.now()}.wav`);
|
|
41
89
|
writeFileSync(tmpFile, wavBuffer);
|
|
@@ -51,30 +99,28 @@ function playWav(wavBuffer) {
|
|
|
51
99
|
playerArgs = ["-q", tmpFile];
|
|
52
100
|
}
|
|
53
101
|
else {
|
|
54
|
-
// Windows fallback via PowerShell
|
|
55
102
|
playerCmd = "powershell";
|
|
56
103
|
playerArgs = ["-c", `(New-Object System.Media.SoundPlayer '${tmpFile}').PlaySync()`];
|
|
57
104
|
}
|
|
58
|
-
stopPlayback();
|
|
105
|
+
stopPlayback();
|
|
59
106
|
const child = spawn(playerCmd, playerArgs, { stdio: "ignore", detached: false });
|
|
60
107
|
playbackProcess = child;
|
|
61
108
|
child.on("close", () => {
|
|
62
109
|
try {
|
|
63
110
|
unlinkSync(tmpFile);
|
|
64
111
|
}
|
|
65
|
-
catch { /* ignore
|
|
112
|
+
catch { /* ignore */ }
|
|
66
113
|
if (playbackProcess === child)
|
|
67
114
|
playbackProcess = null;
|
|
68
115
|
});
|
|
69
116
|
child.on("error", () => {
|
|
70
|
-
// Silently ignore โ player may not be installed
|
|
71
117
|
try {
|
|
72
118
|
unlinkSync(tmpFile);
|
|
73
119
|
}
|
|
74
120
|
catch { /* ignore */ }
|
|
75
121
|
});
|
|
76
122
|
}
|
|
77
|
-
// โโ WAV
|
|
123
|
+
// โโ WAV Builder โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
78
124
|
function buildWavHeader(dataLength, sampleRate = 24000, channels = 1, bitsPerSample = 16) {
|
|
79
125
|
const byteRate = sampleRate * channels * bitsPerSample / 8;
|
|
80
126
|
const blockAlign = channels * bitsPerSample / 8;
|
|
@@ -94,40 +140,39 @@ function buildWavHeader(dataLength, sampleRate = 24000, channels = 1, bitsPerSam
|
|
|
94
140
|
header.writeUInt32LE(dataLength, 40);
|
|
95
141
|
return header;
|
|
96
142
|
}
|
|
97
|
-
// โโ TTS Synthesis
|
|
143
|
+
// โโ TTS Synthesis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
98
144
|
/**
|
|
99
|
-
* Synthesizes `text` via Gemini TTS
|
|
100
|
-
*
|
|
101
|
-
* REPL is never disrupted by TTS failures.
|
|
145
|
+
* Synthesizes `text` via Gemini TTS using the CareerVivid API key for auth.
|
|
146
|
+
* Non-blocking โ errors are silently swallowed so the REPL is never disrupted.
|
|
102
147
|
*/
|
|
103
|
-
export async function speakText(text,
|
|
148
|
+
export async function speakText(text, _unusedKey) {
|
|
104
149
|
if (!text.trim())
|
|
105
150
|
return;
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
108
|
-
return; // No key โ silently skip
|
|
109
|
-
// Strip markdown
|
|
151
|
+
const geminiKey = await fetchGeminiKey();
|
|
152
|
+
if (!geminiKey)
|
|
153
|
+
return; // No key available โ silently skip
|
|
154
|
+
// Strip markdown for natural-sounding speech
|
|
110
155
|
const cleaned = text
|
|
111
|
-
.replace(/```[\s\S]*?```/g, "")
|
|
112
|
-
.replace(/`[^`]+`/g, "")
|
|
113
|
-
.replace(/\*\*(.*?)\*\*/g, "$1")
|
|
114
|
-
.replace(/\*(.*?)\*/g, "$1")
|
|
115
|
-
.replace(/^[#>โข\-*]\s*/gm, "")
|
|
156
|
+
.replace(/```[\s\S]*?```/g, "")
|
|
157
|
+
.replace(/`[^`]+`/g, "")
|
|
158
|
+
.replace(/\*\*(.*?)\*\*/g, "$1")
|
|
159
|
+
.replace(/\*(.*?)\*/g, "$1")
|
|
160
|
+
.replace(/^[#>โข\-*]\s*/gm, "")
|
|
116
161
|
.replace(/\s+/g, " ")
|
|
117
162
|
.trim()
|
|
118
|
-
.slice(0, 1000);
|
|
163
|
+
.slice(0, 1000);
|
|
119
164
|
if (!cleaned)
|
|
120
165
|
return;
|
|
121
166
|
try {
|
|
122
|
-
const ai = new GoogleGenAI({ apiKey:
|
|
167
|
+
const ai = new GoogleGenAI({ apiKey: geminiKey });
|
|
123
168
|
const response = await ai.models.generateContent({
|
|
124
|
-
model:
|
|
169
|
+
model: currentTtsModel,
|
|
125
170
|
contents: [{ parts: [{ text: cleaned }] }],
|
|
126
171
|
config: {
|
|
127
172
|
responseModalities: [Modality.AUDIO],
|
|
128
173
|
speechConfig: {
|
|
129
174
|
voiceConfig: {
|
|
130
|
-
prebuiltVoiceConfig: { voiceName:
|
|
175
|
+
prebuiltVoiceConfig: { voiceName: currentVoice },
|
|
131
176
|
},
|
|
132
177
|
},
|
|
133
178
|
},
|
|
@@ -142,12 +187,12 @@ export async function speakText(text, apiKey) {
|
|
|
142
187
|
if (audioParts.length === 0)
|
|
143
188
|
return;
|
|
144
189
|
const pcmData = Buffer.concat(audioParts);
|
|
145
|
-
const
|
|
146
|
-
const wavBuffer = Buffer.concat([wavHeader, pcmData]);
|
|
147
|
-
// Fire and forget โ does not block the REPL
|
|
190
|
+
const wavBuffer = Buffer.concat([buildWavHeader(pcmData.length), pcmData]);
|
|
148
191
|
playWav(wavBuffer);
|
|
149
192
|
}
|
|
150
193
|
catch {
|
|
151
|
-
// Silently
|
|
194
|
+
// Silently ignore โ TTS errors must never crash the agent REPL
|
|
195
|
+
// Invalidate cached key so we retry fetching on the next call
|
|
196
|
+
cachedGeminiKey = null;
|
|
152
197
|
}
|
|
153
198
|
}
|
package/package.json
CHANGED