opencharm 0.7.1 → 0.8.1
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 +83 -29
- package/package.json +1 -1
- package/sim/charm_sim.wasm +0 -0
package/dist/main.mjs
CHANGED
|
@@ -406,6 +406,25 @@ const ACP_AGENTS = {
|
|
|
406
406
|
};
|
|
407
407
|
const ACP_AGENT_NAMES = Object.keys(ACP_AGENTS);
|
|
408
408
|
//#endregion
|
|
409
|
+
//#region ../design/src/own-colour.ts
|
|
410
|
+
const OWN_COLOUR = /^#[0-9A-Fa-f]{6}$/;
|
|
411
|
+
function linear(v) {
|
|
412
|
+
return v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
|
|
413
|
+
}
|
|
414
|
+
function ownColourProblem(hex) {
|
|
415
|
+
const [r, g, b] = [
|
|
416
|
+
1,
|
|
417
|
+
3,
|
|
418
|
+
5
|
|
419
|
+
].map((at) => Number.parseInt(hex.slice(at, at + 2), 16) / 255);
|
|
420
|
+
if (.2126 * linear(r) + .7152 * linear(g) + .0722 * linear(b) < .05) return "too dark to see on black";
|
|
421
|
+
const max = Math.max(r, g, b);
|
|
422
|
+
const spread = max - Math.min(r, g, b);
|
|
423
|
+
if (max !== r || max < .5 || spread / max < .5) return void 0;
|
|
424
|
+
const hue = 60 * (g - b) / spread;
|
|
425
|
+
return hue >= 6 && hue <= 40 ? "too close to the orange that means 'it needs you'" : void 0;
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
409
428
|
//#region ../protocol/src/constants.ts
|
|
410
429
|
const AUDIO_DOWN = {
|
|
411
430
|
format: "opus",
|
|
@@ -629,22 +648,7 @@ const MAX_NAME_CHARS = 12;
|
|
|
629
648
|
const MAX_NAME_BYTES = 24;
|
|
630
649
|
const MAX_GREETING_BYTES = 40;
|
|
631
650
|
const COLOUR_IDS = facesData.colours.map((colour) => colour.id);
|
|
632
|
-
const OWN_COLOUR = /^#[0-9A-Fa-f]{6}$/;
|
|
633
651
|
const lookName = z.string().min(1, "the name can't be empty").refine((name) => [...name].length <= MAX_NAME_CHARS && utf8Length(name) <= MAX_NAME_BYTES, { message: `at most ${MAX_NAME_CHARS} characters` });
|
|
634
|
-
function ownColourProblem(hex) {
|
|
635
|
-
const [r, g, b] = [
|
|
636
|
-
1,
|
|
637
|
-
3,
|
|
638
|
-
5
|
|
639
|
-
].map((at) => Number.parseInt(hex.slice(at, at + 2), 16) / 255);
|
|
640
|
-
const linear = (v) => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
|
|
641
|
-
if (.2126 * linear(r) + .7152 * linear(g) + .0722 * linear(b) < .05) return "too dark to see on black";
|
|
642
|
-
const max = Math.max(r, g, b);
|
|
643
|
-
const spread = max - Math.min(r, g, b);
|
|
644
|
-
if (max !== r || max < .5 || spread / max < .5) return void 0;
|
|
645
|
-
const hue = 60 * (g - b) / spread;
|
|
646
|
-
return hue >= 6 && hue <= 40 ? "too close to the orange that means 'it needs you'" : void 0;
|
|
647
|
-
}
|
|
648
652
|
const colour = z.string().superRefine((value, context) => {
|
|
649
653
|
if (COLOUR_IDS.includes(value)) return;
|
|
650
654
|
const problem = OWN_COLOUR.test(value) ? ownColourProblem(value) : `the colour is one of ${COLOUR_IDS.join(", ")}, or your own as #RRGGBB`;
|
|
@@ -993,7 +997,7 @@ function sendAdmin(socketPath, request) {
|
|
|
993
997
|
}
|
|
994
998
|
//#endregion
|
|
995
999
|
//#region package.json
|
|
996
|
-
var version = "0.
|
|
1000
|
+
var version = "0.8.1";
|
|
997
1001
|
function channelToCube(value) {
|
|
998
1002
|
if (value < 48) return 0;
|
|
999
1003
|
if (value < 115) return 1;
|
|
@@ -3410,6 +3414,7 @@ var TurnController = class {
|
|
|
3410
3414
|
this.#frames = [];
|
|
3411
3415
|
this.#face("listening");
|
|
3412
3416
|
this.#deps.agent.warm?.(this.#deps.sessionKey);
|
|
3417
|
+
if (this.#deps.speakAloud?.() ?? true) this.#deps.voice.prime?.();
|
|
3413
3418
|
}
|
|
3414
3419
|
audio(frame) {
|
|
3415
3420
|
if (this.#state === "listening" && this.#frames.length < MAX_FRAMES) this.#frames.push(frame);
|
|
@@ -4374,21 +4379,25 @@ function audioOf(frame) {
|
|
|
4374
4379
|
function voiceFor(language, voices) {
|
|
4375
4380
|
return voices[language] ?? voices.en ?? DEFAULT_VOICES.en;
|
|
4376
4381
|
}
|
|
4377
|
-
function
|
|
4378
|
-
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
4382
|
+
function openSocket(options) {
|
|
4379
4383
|
const connection = randomUUID().replace(/-/g, "");
|
|
4380
4384
|
const url = `${options.url ?? SERVICE_URL}?TrustedClientToken=${TRUSTED_CLIENT_TOKEN}&Sec-MS-GEC=${secMsGec()}&Sec-MS-GEC-Version=${GEC_VERSION}&ConnectionId=${connection}`;
|
|
4381
|
-
|
|
4382
|
-
const reader = new WebmOpusReader();
|
|
4383
|
-
let sent = 0;
|
|
4384
|
-
let settled = false;
|
|
4385
|
-
const socket = new WebSocket(url, {
|
|
4385
|
+
return new WebSocket(url, {
|
|
4386
4386
|
headers: {
|
|
4387
4387
|
"User-Agent": USER_AGENT,
|
|
4388
4388
|
Origin: ORIGIN
|
|
4389
4389
|
},
|
|
4390
|
-
handshakeTimeout: timeoutMs
|
|
4390
|
+
handshakeTimeout: options.timeoutMs ?? 15e3
|
|
4391
4391
|
});
|
|
4392
|
+
}
|
|
4393
|
+
function connect(text, language, voices, signal, options, ready) {
|
|
4394
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
4395
|
+
const connection = randomUUID().replace(/-/g, "");
|
|
4396
|
+
const packets = createChannel();
|
|
4397
|
+
const reader = new WebmOpusReader();
|
|
4398
|
+
let sent = 0;
|
|
4399
|
+
let settled = false;
|
|
4400
|
+
const socket = ready ?? openSocket(options);
|
|
4392
4401
|
const finish = (error) => {
|
|
4393
4402
|
if (settled) return;
|
|
4394
4403
|
settled = true;
|
|
@@ -4403,7 +4412,7 @@ function connect(text, language, voices, signal, options) {
|
|
|
4403
4412
|
const timer = setTimeout(() => finish(/* @__PURE__ */ new Error("Microsoft's voice took too long")), timeoutMs);
|
|
4404
4413
|
if (signal.aborted) onAbort();
|
|
4405
4414
|
else signal.addEventListener("abort", onAbort, { once: true });
|
|
4406
|
-
|
|
4415
|
+
const request = () => {
|
|
4407
4416
|
socket.send(message("speech.config", "application/json; charset=utf-8", JSON.stringify({ context: { synthesis: { audio: {
|
|
4408
4417
|
metadataoptions: {
|
|
4409
4418
|
sentenceBoundaryEnabled: "false",
|
|
@@ -4412,7 +4421,9 @@ function connect(text, language, voices, signal, options) {
|
|
|
4412
4421
|
outputFormat: OUTPUT_FORMAT
|
|
4413
4422
|
} } } })));
|
|
4414
4423
|
socket.send(message("ssml", "application/ssml+xml", ssml(text, voiceFor(language, voices)), connection));
|
|
4415
|
-
}
|
|
4424
|
+
};
|
|
4425
|
+
if (socket.readyState === WebSocket.OPEN) request();
|
|
4426
|
+
else socket.once("open", request);
|
|
4416
4427
|
socket.on("message", (data, isBinary) => {
|
|
4417
4428
|
const frame = Buffer.isBuffer(data) ? data : Buffer.concat(data);
|
|
4418
4429
|
if (!isBinary) {
|
|
@@ -4440,9 +4451,48 @@ function createMicrosoftSpeaker(options = {}) {
|
|
|
4440
4451
|
...DEFAULT_VOICES,
|
|
4441
4452
|
...options.voices
|
|
4442
4453
|
};
|
|
4443
|
-
const
|
|
4454
|
+
const spareMs = options.spareMs ?? 2e4;
|
|
4455
|
+
let spare;
|
|
4456
|
+
const takeSpare = () => {
|
|
4457
|
+
const taken = spare;
|
|
4458
|
+
spare = void 0;
|
|
4459
|
+
if (!taken) return void 0;
|
|
4460
|
+
if ((taken.socket.readyState === WebSocket.OPEN || taken.socket.readyState === WebSocket.CONNECTING) && Date.now() < taken.until) return taken.socket;
|
|
4461
|
+
taken.socket.terminate();
|
|
4462
|
+
};
|
|
4463
|
+
const prime = () => {
|
|
4464
|
+
if (spare) return;
|
|
4465
|
+
const socket = openSocket(options);
|
|
4466
|
+
socket.on("error", () => void 0);
|
|
4467
|
+
spare = {
|
|
4468
|
+
socket,
|
|
4469
|
+
until: Date.now() + spareMs
|
|
4470
|
+
};
|
|
4471
|
+
setTimeout(() => {
|
|
4472
|
+
if (spare?.socket !== socket) return;
|
|
4473
|
+
spare = void 0;
|
|
4474
|
+
socket.terminate();
|
|
4475
|
+
}, spareMs).unref();
|
|
4476
|
+
};
|
|
4477
|
+
async function* stream(text, signal, language = "en") {
|
|
4478
|
+
const ready = takeSpare();
|
|
4479
|
+
if (ready) {
|
|
4480
|
+
let spoke = false;
|
|
4481
|
+
try {
|
|
4482
|
+
for await (const packet of connect(text, language, voices, signal, options, ready)) {
|
|
4483
|
+
spoke = true;
|
|
4484
|
+
yield packet;
|
|
4485
|
+
}
|
|
4486
|
+
return;
|
|
4487
|
+
} catch (error) {
|
|
4488
|
+
if (spoke || signal.aborted) throw error;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
yield* connect(text, language, voices, signal, options);
|
|
4492
|
+
}
|
|
4444
4493
|
return {
|
|
4445
4494
|
name: "microsoft",
|
|
4495
|
+
prime,
|
|
4446
4496
|
stream,
|
|
4447
4497
|
async synthesize(text, signal, language = "en") {
|
|
4448
4498
|
const packets = [];
|
|
@@ -4687,6 +4737,9 @@ function speakWithFallback(chain, options = {}) {
|
|
|
4687
4737
|
},
|
|
4688
4738
|
warm: async () => {
|
|
4689
4739
|
await Promise.allSettled(chain.map((speaker) => speaker.warm?.() ?? Promise.resolve()));
|
|
4740
|
+
},
|
|
4741
|
+
prime: () => {
|
|
4742
|
+
if ((resting.get(first) ?? 0) <= now()) first.prime?.();
|
|
4690
4743
|
}
|
|
4691
4744
|
};
|
|
4692
4745
|
}
|
|
@@ -4746,7 +4799,8 @@ function createVoiceFromConfig(config) {
|
|
|
4746
4799
|
language: config.voice.language,
|
|
4747
4800
|
warm: async () => {
|
|
4748
4801
|
await Promise.allSettled([listener.warm?.(), speaker.warm?.()]);
|
|
4749
|
-
}
|
|
4802
|
+
},
|
|
4803
|
+
...speaker.prime ? { prime: speaker.prime } : {}
|
|
4750
4804
|
};
|
|
4751
4805
|
}
|
|
4752
4806
|
function charmToolsServers(config, tools) {
|
|
@@ -4965,7 +5019,7 @@ function formatIdentity(unit, version, commit) {
|
|
|
4965
5019
|
let cached;
|
|
4966
5020
|
function identity() {
|
|
4967
5021
|
if (cached) return cached;
|
|
4968
|
-
const commit = "
|
|
5022
|
+
const commit = "e06b6db";
|
|
4969
5023
|
cached = {
|
|
4970
5024
|
version: `cli@${version}`,
|
|
4971
5025
|
commit,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencharm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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
|