@flowingspring/dsh-voco 0.3.6 → 0.3.8
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/README.md +6 -0
- package/README.zh.md +7 -1
- package/lib/client.js +546 -58
- package/lib/client.js.map +1 -1
- package/lib/plugins/voice-local.js +59 -20
- package/lib/plugins/voice-web.js +2 -1
- package/lib/plugins/voice.js +4 -0
- package/package.json +13 -3
|
@@ -5,6 +5,8 @@ import { existsSync } from "node:fs";
|
|
|
5
5
|
import { dirname, join, resolve } from "node:path";
|
|
6
6
|
import { loadEnvFile } from "node:process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
9
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
8
10
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
9
11
|
import { tmpdir } from "node:os";
|
|
10
12
|
import { EdgeTTS } from "node-edge-tts";
|
|
@@ -70,6 +72,9 @@ var LocalSession = class {
|
|
|
70
72
|
async start() {
|
|
71
73
|
await this.backend.start((event) => this.receive(event));
|
|
72
74
|
}
|
|
75
|
+
beginManualUtterance() {
|
|
76
|
+
this.backend.beginManualUtterance();
|
|
77
|
+
}
|
|
73
78
|
appendAudio(audio) {
|
|
74
79
|
this.backend.appendAudio(audio);
|
|
75
80
|
}
|
|
@@ -312,6 +317,8 @@ var NodeSpeechBackend = class {
|
|
|
312
317
|
preRoll = [];
|
|
313
318
|
preRollBytes = 0;
|
|
314
319
|
active;
|
|
320
|
+
manual = false;
|
|
321
|
+
manualMaxed = false;
|
|
315
322
|
recognitionQueue = Promise.resolve();
|
|
316
323
|
synthesisQueue = Promise.resolve();
|
|
317
324
|
synthesisGeneration = 0;
|
|
@@ -343,8 +350,16 @@ var NodeSpeechBackend = class {
|
|
|
343
350
|
emit({ type: "ready" });
|
|
344
351
|
return Promise.resolve();
|
|
345
352
|
}
|
|
353
|
+
beginManualUtterance() {
|
|
354
|
+
if (this.closed) return;
|
|
355
|
+
this.active = void 0;
|
|
356
|
+
this.preRoll.splice(0);
|
|
357
|
+
this.preRollBytes = 0;
|
|
358
|
+
this.manual = true;
|
|
359
|
+
this.manualMaxed = false;
|
|
360
|
+
}
|
|
346
361
|
appendAudio(audio) {
|
|
347
|
-
if (this.closed || audio.byteLength === 0) return;
|
|
362
|
+
if (this.closed || audio.byteLength === 0 || this.manualMaxed) return;
|
|
348
363
|
if (audio.byteLength % 2 !== 0) {
|
|
349
364
|
this.emit?.({
|
|
350
365
|
type: "error",
|
|
@@ -367,14 +382,19 @@ var NodeSpeechBackend = class {
|
|
|
367
382
|
this.active.silenceBytes = 0;
|
|
368
383
|
if (!this.active.confirmed && this.active.voicedBytes >= this.bytesFor(this.config.minSpeechDurationMs ?? 250)) this.confirmUtterance();
|
|
369
384
|
} else this.active.silenceBytes += frame.byteLength;
|
|
370
|
-
if (!this.active.confirmed && this.active.silenceBytes >= this.bytesFor(this.config.minSpeechDurationMs ?? 250)) {
|
|
385
|
+
if (!this.manual && !this.active.confirmed && this.active.silenceBytes >= this.bytesFor(this.config.minSpeechDurationMs ?? 250)) {
|
|
371
386
|
this.active = void 0;
|
|
372
387
|
return;
|
|
373
388
|
}
|
|
374
|
-
if (this.active.silenceBytes >= this.bytesFor(this.config.silenceDurationMs) || this.active.totalBytes >= this.bytesFor(this.config.maxUtteranceMs))
|
|
389
|
+
if (!this.manual && this.active.silenceBytes >= this.bytesFor(this.config.silenceDurationMs) || this.active.totalBytes >= this.bytesFor(this.config.maxUtteranceMs)) {
|
|
390
|
+
this.finishUtterance();
|
|
391
|
+
if (this.manual) this.manualMaxed = true;
|
|
392
|
+
}
|
|
375
393
|
}
|
|
376
394
|
commitAudio() {
|
|
377
395
|
this.finishUtterance();
|
|
396
|
+
this.manual = false;
|
|
397
|
+
this.manualMaxed = false;
|
|
378
398
|
}
|
|
379
399
|
synthesize(responseId, text) {
|
|
380
400
|
const generation = this.synthesisGeneration;
|
|
@@ -433,6 +453,8 @@ var NodeSpeechBackend = class {
|
|
|
433
453
|
this.synthesisGeneration += 1;
|
|
434
454
|
this.synthesisResponses.clear();
|
|
435
455
|
this.active = void 0;
|
|
456
|
+
this.manual = false;
|
|
457
|
+
this.manualMaxed = false;
|
|
436
458
|
this.preRoll.splice(0);
|
|
437
459
|
await Promise.allSettled([this.recognitionQueue, this.synthesisQueue]);
|
|
438
460
|
this.emit?.({
|
|
@@ -539,8 +561,10 @@ function errorMessage(error) {
|
|
|
539
561
|
/** Silence-gated SiliconFlow cloud ASR with Edge TTS. */
|
|
540
562
|
const name = "voice-local";
|
|
541
563
|
const inject = ["voice"];
|
|
564
|
+
const VOICE_LOCAL_SETTINGS_NAMESPACE = settingsNamespace("voice-local");
|
|
565
|
+
const SILICONFLOW_API_KEY_REF = credentialRef("SILICONFLOW_API_KEY");
|
|
542
566
|
const Config = z.object({
|
|
543
|
-
apiKey: z.string(),
|
|
567
|
+
apiKey: z.string().role("secret"),
|
|
544
568
|
endpoint: z.string().default("https://api.siliconflow.cn/v1/audio/transcriptions"),
|
|
545
569
|
model: z.string().default("XingChenAGI/XingChenASR-V3.2-Ultra"),
|
|
546
570
|
interactionMode: z.union(["speech-shell", "frontend-agent"]).default("speech-shell"),
|
|
@@ -575,26 +599,34 @@ function loadProjectEnv() {
|
|
|
575
599
|
}
|
|
576
600
|
}
|
|
577
601
|
function apply(ctx, config = {}) {
|
|
578
|
-
loadProjectEnv();
|
|
602
|
+
if (ctx.get("credentials") === void 0) loadProjectEnv();
|
|
603
|
+
let current = () => config;
|
|
604
|
+
installSettingsSection(ctx, VOICE_LOCAL_SETTINGS_NAMESPACE, Config, config, {
|
|
605
|
+
setSource: (source) => {
|
|
606
|
+
current = source;
|
|
607
|
+
},
|
|
608
|
+
onChange: () => {}
|
|
609
|
+
});
|
|
579
610
|
return ctx.voice.registerProvider({
|
|
580
611
|
id: "local",
|
|
581
612
|
available: () => true,
|
|
582
613
|
connect: async ({ voiceSessionId, emit }) => {
|
|
614
|
+
const active = current();
|
|
583
615
|
const session = new LocalSession(new NodeSpeechBackend({
|
|
584
|
-
apiKey:
|
|
585
|
-
endpoint:
|
|
586
|
-
model:
|
|
587
|
-
requestTimeoutMs:
|
|
588
|
-
inputSampleRate:
|
|
589
|
-
outputSampleRate:
|
|
590
|
-
ttsRate:
|
|
591
|
-
silenceDurationMs:
|
|
592
|
-
speechThreshold:
|
|
593
|
-
minSpeechDurationMs:
|
|
594
|
-
preRollMs:
|
|
595
|
-
trailingSilenceMs:
|
|
596
|
-
maxUtteranceMs:
|
|
597
|
-
}), emit, voiceSessionId,
|
|
616
|
+
apiKey: await resolveSiliconFlowApiKey(ctx, active),
|
|
617
|
+
endpoint: active.endpoint ?? "https://api.siliconflow.cn/v1/audio/transcriptions",
|
|
618
|
+
model: active.model ?? "XingChenAGI/XingChenASR-V3.2-Ultra",
|
|
619
|
+
requestTimeoutMs: active.requestTimeoutMs ?? 6e4,
|
|
620
|
+
inputSampleRate: active.inputSampleRate ?? 16e3,
|
|
621
|
+
outputSampleRate: active.outputSampleRate ?? 48e3,
|
|
622
|
+
ttsRate: active.ttsRate ?? "+20%",
|
|
623
|
+
silenceDurationMs: active.silenceDurationMs ?? 1500,
|
|
624
|
+
speechThreshold: active.speechThreshold ?? .015,
|
|
625
|
+
minSpeechDurationMs: active.minSpeechDurationMs ?? 250,
|
|
626
|
+
preRollMs: active.preRollMs ?? 400,
|
|
627
|
+
trailingSilenceMs: active.trailingSilenceMs ?? 200,
|
|
628
|
+
maxUtteranceMs: active.maxUtteranceMs ?? 6e4
|
|
629
|
+
}), emit, voiceSessionId, active.interactionMode ?? "speech-shell");
|
|
598
630
|
try {
|
|
599
631
|
await session.start();
|
|
600
632
|
return session;
|
|
@@ -605,5 +637,12 @@ function apply(ctx, config = {}) {
|
|
|
605
637
|
}
|
|
606
638
|
});
|
|
607
639
|
}
|
|
640
|
+
/** Resolve the key per connection so a settings-page write reaches the next attempt. */
|
|
641
|
+
async function resolveSiliconFlowApiKey(ctx, config) {
|
|
642
|
+
if (config.apiKey !== void 0 && config.apiKey.trim() !== "") return config.apiKey;
|
|
643
|
+
const credentials = ctx.get("credentials");
|
|
644
|
+
if (credentials !== void 0) return (await credentials.resolve(SILICONFLOW_API_KEY_REF))?.value ?? "";
|
|
645
|
+
return process.env.SILICONFLOW_API_KEY ?? "";
|
|
646
|
+
}
|
|
608
647
|
//#endregion
|
|
609
|
-
export { Config, LocalSession, NodeSpeechBackend, SiliconFlowAsr, apply, inject, name, pcm16MonoWav };
|
|
648
|
+
export { Config, LocalSession, NodeSpeechBackend, SILICONFLOW_API_KEY_REF, SiliconFlowAsr, VOICE_LOCAL_SETTINGS_NAMESPACE, apply, inject, name, pcm16MonoWav, resolveSiliconFlowApiKey };
|
package/lib/plugins/voice-web.js
CHANGED
|
@@ -213,7 +213,8 @@ async function attach(ctx, socket, rawUrl) {
|
|
|
213
213
|
return;
|
|
214
214
|
}
|
|
215
215
|
const type = control.type;
|
|
216
|
-
if (type === "audio.
|
|
216
|
+
if (type === "audio.push-to-talk.start") voice.beginManualUtterance(session.id);
|
|
217
|
+
else if (type === "audio.commit") voice.commitAudio(session.id);
|
|
217
218
|
else if (type === "response.interrupt") voice.interruptResponse(session.id);
|
|
218
219
|
else if (type === "playback.ended") voice.playbackEnded(session.id);
|
|
219
220
|
else if (type === "task.cancel") {
|
package/lib/plugins/voice.js
CHANGED
|
@@ -169,6 +169,10 @@ var VoiceRuntime = class extends Service {
|
|
|
169
169
|
appendAudio(id, audio) {
|
|
170
170
|
this.requireAttachedSession(id).provider.appendAudio(audio);
|
|
171
171
|
}
|
|
172
|
+
/** Begin a manually committed push-to-talk utterance. */
|
|
173
|
+
beginManualUtterance(id) {
|
|
174
|
+
this.requireAttachedSession(id).provider.beginManualUtterance();
|
|
175
|
+
}
|
|
172
176
|
/**
|
|
173
177
|
* Commit current microphone input.
|
|
174
178
|
* @param id - voice session.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowingspring/dsh-voco",
|
|
3
3
|
"description": "Persistent voice conversations for DSH with cloud speech recognition, Edge TTS, and background Agent delegation",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.8",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"dsh",
|
|
@@ -65,6 +65,8 @@
|
|
|
65
65
|
"@deepseek-ai/dsh-client-connection",
|
|
66
66
|
"@deepseek-ai/dsh-client-locale",
|
|
67
67
|
"@deepseek-ai/dsh-client-runtime",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins",
|
|
68
70
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
69
71
|
"@deepseek-ai/dsh-client-ui-layout",
|
|
70
72
|
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
@@ -86,15 +88,19 @@
|
|
|
86
88
|
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
|
|
87
89
|
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.1",
|
|
88
90
|
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
|
|
91
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
|
|
89
93
|
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.1",
|
|
90
94
|
"@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.1",
|
|
91
95
|
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.1-rc.1",
|
|
92
96
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.1",
|
|
93
97
|
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
|
|
94
98
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
99
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
95
100
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
96
101
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
97
102
|
"@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.1",
|
|
103
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
98
104
|
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
|
|
99
105
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
100
106
|
"@deepseek-ai/dsh-workspace": "^0.1.1-rc.1",
|
|
@@ -109,15 +115,19 @@
|
|
|
109
115
|
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
|
|
110
116
|
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.1",
|
|
111
117
|
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.1",
|
|
118
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.1-rc.2",
|
|
119
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.1-rc.2",
|
|
112
120
|
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.1",
|
|
113
121
|
"@deepseek-ai/dsh-client-ui-layout": "^0.1.1-rc.1",
|
|
114
122
|
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.1-rc.1",
|
|
115
123
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.1-rc.1",
|
|
116
124
|
"@deepseek-ai/dsh-host-webserver": "^0.1.1-rc.1",
|
|
117
125
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
126
|
+
"@deepseek-ai/dsh-credentials": "^0.1.1-rc.2",
|
|
118
127
|
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
119
128
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
120
129
|
"@deepseek-ai/dsh-session-persistence": "^0.1.1-rc.1",
|
|
130
|
+
"@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
|
|
121
131
|
"@deepseek-ai/dsh-system-prompt": "^0.1.1-rc.1",
|
|
122
132
|
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
123
133
|
"@deepseek-ai/dsh-workspace": "^0.1.1-rc.1",
|
|
@@ -125,11 +135,11 @@
|
|
|
125
135
|
"@types/ws": "^8.18.1",
|
|
126
136
|
"react": "^18.2.0",
|
|
127
137
|
"react-dom": "^18.2.0",
|
|
138
|
+
"@flowingspring/dsh-voice": "0.1.2",
|
|
128
139
|
"@flowingspring/dsh-voice-local": "0.1.0",
|
|
129
|
-
"@flowingspring/dsh-client-ui-voice": "0.1.2",
|
|
130
140
|
"@flowingspring/dsh-llm-tool-call-compat": "0.1.0",
|
|
131
|
-
"@flowingspring/dsh-voice": "0.1.2",
|
|
132
141
|
"@flowingspring/dsh-voice-web": "0.1.2",
|
|
142
|
+
"@flowingspring/dsh-client-ui-voice": "0.1.2",
|
|
133
143
|
"@flowingspring/dsh-voice-assistant": "0.1.2"
|
|
134
144
|
}
|
|
135
145
|
}
|