opencharm 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.mjs +102 -16
- package/package.json +1 -1
- package/sim/charm_sim.wasm +0 -0
package/dist/main.mjs
CHANGED
|
@@ -997,7 +997,7 @@ function sendAdmin(socketPath, request) {
|
|
|
997
997
|
}
|
|
998
998
|
//#endregion
|
|
999
999
|
//#region package.json
|
|
1000
|
-
var version = "0.8.
|
|
1000
|
+
var version = "0.8.2";
|
|
1001
1001
|
function channelToCube(value) {
|
|
1002
1002
|
if (value < 48) return 0;
|
|
1003
1003
|
if (value < 115) return 1;
|
|
@@ -3361,6 +3361,10 @@ var TurnController = class {
|
|
|
3361
3361
|
#generation = 0;
|
|
3362
3362
|
#controller;
|
|
3363
3363
|
#speaking = false;
|
|
3364
|
+
#questions = 0;
|
|
3365
|
+
#questionsEnd;
|
|
3366
|
+
#endQuestions = () => {};
|
|
3367
|
+
#questionStarts = /* @__PURE__ */ new Set();
|
|
3364
3368
|
#playedBy = 0;
|
|
3365
3369
|
#asking;
|
|
3366
3370
|
#endFace;
|
|
@@ -3414,6 +3418,7 @@ var TurnController = class {
|
|
|
3414
3418
|
this.#frames = [];
|
|
3415
3419
|
this.#face("listening");
|
|
3416
3420
|
this.#deps.agent.warm?.(this.#deps.sessionKey);
|
|
3421
|
+
if (this.#deps.speakAloud?.() ?? true) this.#deps.voice.prime?.();
|
|
3417
3422
|
}
|
|
3418
3423
|
audio(frame) {
|
|
3419
3424
|
if (this.#state === "listening" && this.#frames.length < MAX_FRAMES) this.#frames.push(frame);
|
|
@@ -3506,6 +3511,12 @@ var TurnController = class {
|
|
|
3506
3511
|
if (ask) this.#asking = async (text, options) => {
|
|
3507
3512
|
asking = true;
|
|
3508
3513
|
clearTimeout(timer);
|
|
3514
|
+
if (this.#questions++ === 0) {
|
|
3515
|
+
this.#questionsEnd = new Promise((resolve) => {
|
|
3516
|
+
this.#endQuestions = resolve;
|
|
3517
|
+
});
|
|
3518
|
+
for (const notify of this.#questionStarts) notify();
|
|
3519
|
+
}
|
|
3509
3520
|
try {
|
|
3510
3521
|
return await ask(text, {
|
|
3511
3522
|
...options,
|
|
@@ -3513,6 +3524,10 @@ var TurnController = class {
|
|
|
3513
3524
|
});
|
|
3514
3525
|
} finally {
|
|
3515
3526
|
asking = false;
|
|
3527
|
+
if (--this.#questions === 0) {
|
|
3528
|
+
this.#questionsEnd = void 0;
|
|
3529
|
+
this.#endQuestions();
|
|
3530
|
+
}
|
|
3516
3531
|
if (!signal.aborted) timer = startClock();
|
|
3517
3532
|
}
|
|
3518
3533
|
};
|
|
@@ -3721,14 +3736,36 @@ var TurnController = class {
|
|
|
3721
3736
|
state: "start"
|
|
3722
3737
|
});
|
|
3723
3738
|
}
|
|
3739
|
+
const sleep = this.#deps.sleep ?? sleepMs;
|
|
3740
|
+
const now = this.#deps.now ?? Date.now;
|
|
3741
|
+
const aborted = new Promise((resolve) => signal.addEventListener("abort", () => resolve("aborted"), { once: true }));
|
|
3724
3742
|
this.#deps.send({
|
|
3725
3743
|
type: "tts",
|
|
3726
3744
|
state: "sentence_start",
|
|
3727
3745
|
text
|
|
3728
3746
|
});
|
|
3729
|
-
const sleep = this.#deps.sleep ?? sleepMs;
|
|
3730
3747
|
clock.pause();
|
|
3731
|
-
|
|
3748
|
+
let remaining = readingMs(text);
|
|
3749
|
+
while (going()) {
|
|
3750
|
+
if (this.#questionsEnd) {
|
|
3751
|
+
await Promise.race([this.#questionsEnd, aborted]);
|
|
3752
|
+
continue;
|
|
3753
|
+
}
|
|
3754
|
+
const from = now();
|
|
3755
|
+
let notify = () => {};
|
|
3756
|
+
const question = new Promise((resolve) => {
|
|
3757
|
+
notify = () => resolve("question");
|
|
3758
|
+
});
|
|
3759
|
+
this.#questionStarts.add(notify);
|
|
3760
|
+
const outcome = await Promise.race([
|
|
3761
|
+
sleep(remaining).then(() => "read"),
|
|
3762
|
+
aborted,
|
|
3763
|
+
question
|
|
3764
|
+
]);
|
|
3765
|
+
this.#questionStarts.delete(notify);
|
|
3766
|
+
if (outcome !== "question") break;
|
|
3767
|
+
remaining = Math.max(0, remaining - (now() - from));
|
|
3768
|
+
}
|
|
3732
3769
|
clock.resume();
|
|
3733
3770
|
}
|
|
3734
3771
|
#failureLine(error) {
|
|
@@ -4378,21 +4415,25 @@ function audioOf(frame) {
|
|
|
4378
4415
|
function voiceFor(language, voices) {
|
|
4379
4416
|
return voices[language] ?? voices.en ?? DEFAULT_VOICES.en;
|
|
4380
4417
|
}
|
|
4381
|
-
function
|
|
4382
|
-
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
4418
|
+
function openSocket(options) {
|
|
4383
4419
|
const connection = randomUUID().replace(/-/g, "");
|
|
4384
4420
|
const url = `${options.url ?? SERVICE_URL}?TrustedClientToken=${TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGec()}&Sec-MS-GEC-Version=${GEC_VERSION}&ConnectionId=${connection}`;
|
|
4385
|
-
|
|
4386
|
-
const reader = new WebmOpusReader();
|
|
4387
|
-
let sent = 0;
|
|
4388
|
-
let settled = false;
|
|
4389
|
-
const socket = new WebSocket(url, {
|
|
4421
|
+
return new WebSocket(url, {
|
|
4390
4422
|
headers: {
|
|
4391
4423
|
"User-Agent": USER_AGENT,
|
|
4392
4424
|
Origin: ORIGIN
|
|
4393
4425
|
},
|
|
4394
|
-
handshakeTimeout: timeoutMs
|
|
4426
|
+
handshakeTimeout: options.timeoutMs ?? 15e3
|
|
4395
4427
|
});
|
|
4428
|
+
}
|
|
4429
|
+
function connect(text, language, voices, signal, options, ready) {
|
|
4430
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
4431
|
+
const connection = randomUUID().replace(/-/g, "");
|
|
4432
|
+
const packets = createChannel();
|
|
4433
|
+
const reader = new WebmOpusReader();
|
|
4434
|
+
let sent = 0;
|
|
4435
|
+
let settled = false;
|
|
4436
|
+
const socket = ready ?? openSocket(options);
|
|
4396
4437
|
const finish = (error) => {
|
|
4397
4438
|
if (settled) return;
|
|
4398
4439
|
settled = true;
|
|
@@ -4407,7 +4448,7 @@ function connect(text, language, voices, signal, options) {
|
|
|
4407
4448
|
const timer = setTimeout(() => finish(/* @__PURE__ */ new Error("Microsoft's voice took too long")), timeoutMs);
|
|
4408
4449
|
if (signal.aborted) onAbort();
|
|
4409
4450
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
4410
|
-
|
|
4451
|
+
const request = () => {
|
|
4411
4452
|
socket.send(message("speech.config", "application/json; charset=utf-8", JSON.stringify({ context: { synthesis: { audio: {
|
|
4412
4453
|
metadataoptions: {
|
|
4413
4454
|
sentenceBoundaryEnabled: "false",
|
|
@@ -4416,7 +4457,9 @@ function connect(text, language, voices, signal, options) {
|
|
|
4416
4457
|
outputFormat: OUTPUT_FORMAT
|
|
4417
4458
|
} } } })));
|
|
4418
4459
|
socket.send(message("ssml", "application/ssml+xml", ssml(text, voiceFor(language, voices)), connection));
|
|
4419
|
-
}
|
|
4460
|
+
};
|
|
4461
|
+
if (socket.readyState === WebSocket.OPEN) request();
|
|
4462
|
+
else socket.once("open", request);
|
|
4420
4463
|
socket.on("message", (data, isBinary) => {
|
|
4421
4464
|
const frame = Buffer.isBuffer(data) ? data : Buffer.concat(data);
|
|
4422
4465
|
if (!isBinary) {
|
|
@@ -4444,9 +4487,48 @@ function createMicrosoftSpeaker(options = {}) {
|
|
|
4444
4487
|
...DEFAULT_VOICES,
|
|
4445
4488
|
...options.voices
|
|
4446
4489
|
};
|
|
4447
|
-
const
|
|
4490
|
+
const spareMs = options.spareMs ?? 2e4;
|
|
4491
|
+
let spare;
|
|
4492
|
+
const takeSpare = () => {
|
|
4493
|
+
const taken = spare;
|
|
4494
|
+
spare = void 0;
|
|
4495
|
+
if (!taken) return void 0;
|
|
4496
|
+
if ((taken.socket.readyState === WebSocket.OPEN || taken.socket.readyState === WebSocket.CONNECTING) && Date.now() < taken.until) return taken.socket;
|
|
4497
|
+
taken.socket.terminate();
|
|
4498
|
+
};
|
|
4499
|
+
const prime = () => {
|
|
4500
|
+
if (spare) return;
|
|
4501
|
+
const socket = openSocket(options);
|
|
4502
|
+
socket.on("error", () => void 0);
|
|
4503
|
+
spare = {
|
|
4504
|
+
socket,
|
|
4505
|
+
until: Date.now() + spareMs
|
|
4506
|
+
};
|
|
4507
|
+
setTimeout(() => {
|
|
4508
|
+
if (spare?.socket !== socket) return;
|
|
4509
|
+
spare = void 0;
|
|
4510
|
+
socket.terminate();
|
|
4511
|
+
}, spareMs).unref();
|
|
4512
|
+
};
|
|
4513
|
+
async function* stream(text, signal, language = "en") {
|
|
4514
|
+
const ready = takeSpare();
|
|
4515
|
+
if (ready) {
|
|
4516
|
+
let spoke = false;
|
|
4517
|
+
try {
|
|
4518
|
+
for await (const packet of connect(text, language, voices, signal, options, ready)) {
|
|
4519
|
+
spoke = true;
|
|
4520
|
+
yield packet;
|
|
4521
|
+
}
|
|
4522
|
+
return;
|
|
4523
|
+
} catch (error) {
|
|
4524
|
+
if (spoke || signal.aborted) throw error;
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
yield* connect(text, language, voices, signal, options);
|
|
4528
|
+
}
|
|
4448
4529
|
return {
|
|
4449
4530
|
name: "microsoft",
|
|
4531
|
+
prime,
|
|
4450
4532
|
stream,
|
|
4451
4533
|
async synthesize(text, signal, language = "en") {
|
|
4452
4534
|
const packets = [];
|
|
@@ -4691,6 +4773,9 @@ function speakWithFallback(chain, options = {}) {
|
|
|
4691
4773
|
},
|
|
4692
4774
|
warm: async () => {
|
|
4693
4775
|
await Promise.allSettled(chain.map((speaker) => speaker.warm?.() ?? Promise.resolve()));
|
|
4776
|
+
},
|
|
4777
|
+
prime: () => {
|
|
4778
|
+
if ((resting.get(first) ?? 0) <= now()) first.prime?.();
|
|
4694
4779
|
}
|
|
4695
4780
|
};
|
|
4696
4781
|
}
|
|
@@ -4750,7 +4835,8 @@ function createVoiceFromConfig(config) {
|
|
|
4750
4835
|
language: config.voice.language,
|
|
4751
4836
|
warm: async () => {
|
|
4752
4837
|
await Promise.allSettled([listener.warm?.(), speaker.warm?.()]);
|
|
4753
|
-
}
|
|
4838
|
+
},
|
|
4839
|
+
...speaker.prime ? { prime: speaker.prime } : {}
|
|
4754
4840
|
};
|
|
4755
4841
|
}
|
|
4756
4842
|
function charmToolsServers(config, tools) {
|
|
@@ -4969,7 +5055,7 @@ function formatIdentity(unit, version, commit) {
|
|
|
4969
5055
|
let cached;
|
|
4970
5056
|
function identity() {
|
|
4971
5057
|
if (cached) return cached;
|
|
4972
|
-
const commit = "
|
|
5058
|
+
const commit = "da4ff95";
|
|
4973
5059
|
cached = {
|
|
4974
5060
|
version: `cli@${version}`,
|
|
4975
5061
|
commit,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencharm",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "An open-source body for the AI agent you already run: a face, a voice, one key. Claude Code, Codex, Gemini CLI, Hermes, OpenClaw or any ACP or OpenAI-compatible agent; on a small board, your Mac's notch or the browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/sim/charm_sim.wasm
CHANGED
|
Binary file
|