dsh-realtime-audio-ws 0.1.1 → 0.1.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/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +16 -2
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +12 -0
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +2 -0
- package/lib/types.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +14 -1
- package/src/types.ts +13 -0
package/lib/index.d.ts
CHANGED
|
@@ -22,10 +22,12 @@ export declare const Config: Schema<Schemastery.ObjectS<NoInfer<{
|
|
|
22
22
|
path: Schema<string, string, "defined">;
|
|
23
23
|
maxFrameBytes: Schema<number, number, "defined">;
|
|
24
24
|
maxConnections: Schema<number, number, "defined">;
|
|
25
|
+
openSessionOnConnect: Schema<boolean, boolean, "defined">;
|
|
25
26
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
26
27
|
path: Schema<string, string, "defined">;
|
|
27
28
|
maxFrameBytes: Schema<number, number, "defined">;
|
|
28
29
|
maxConnections: Schema<number, number, "defined">;
|
|
30
|
+
openSessionOnConnect: Schema<boolean, boolean, "defined">;
|
|
29
31
|
}>>, "plain">;
|
|
30
32
|
export declare function apply(ctx: Context, config: RealtimeAudioWsConfig): void;
|
|
31
33
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAOlD,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAEzF,mBAAmB;AACnB,eAAO,MAAM,IAAI,sBAAsB,CAAA;AAEvC,eAAO,MAAM,MAAM;;;;;;;;;;aAQjB,CAAA;AAqBF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAoDvE"}
|
package/lib/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import Schema from '@deepseek-ai/schemastery';
|
|
14
14
|
import { attachAudioSocket } from './bridge.js';
|
|
15
15
|
import { createUpgradeAcceptor, rejectUpgrade } from './upgrade.js';
|
|
16
|
-
import { DEFAULT_MAX_CONNECTIONS, DEFAULT_MAX_FRAME_BYTES, DEFAULT_PATH, } from './types.js';
|
|
16
|
+
import { DEFAULT_MAX_CONNECTIONS, DEFAULT_MAX_FRAME_BYTES, DEFAULT_OPEN_SESSION_ON_CONNECT, DEFAULT_PATH, } from './types.js';
|
|
17
17
|
export * from './types.js';
|
|
18
18
|
export { attachAudioSocket, toBytes } from './bridge.js';
|
|
19
19
|
export { createUpgradeAcceptor, rejectUpgrade } from './upgrade.js';
|
|
@@ -23,6 +23,10 @@ export const Config = Schema.object({
|
|
|
23
23
|
path: Schema.string().default(DEFAULT_PATH),
|
|
24
24
|
maxFrameBytes: Schema.natural().default(DEFAULT_MAX_FRAME_BYTES),
|
|
25
25
|
maxConnections: Schema.natural().default(DEFAULT_MAX_CONNECTIONS),
|
|
26
|
+
// True, and deliberately not the same decision as the agent's `autoStart`. That one opens a session at
|
|
27
|
+
// boot with nobody asking, which is why it is false. This one opens a session because someone connected a
|
|
28
|
+
// microphone: a connection takes an explicit action, an authenticated one, and its absence is silence.
|
|
29
|
+
openSessionOnConnect: Schema.boolean().default(DEFAULT_OPEN_SESSION_ON_CONNECT),
|
|
26
30
|
});
|
|
27
31
|
export function apply(ctx, config) {
|
|
28
32
|
ctx.inject(['connection', 'webServer'], (injected) => {
|
|
@@ -50,11 +54,21 @@ export function apply(ctx, config) {
|
|
|
50
54
|
return;
|
|
51
55
|
}
|
|
52
56
|
clients.add(client);
|
|
57
|
+
// Ask for the session before the bridge goes in, so the first frames are written into a session
|
|
58
|
+
// that is being opened rather than dropped by the mic seam's no-session rule.
|
|
59
|
+
if (config.openSessionOnConnect)
|
|
60
|
+
ctx.emit('realtime-agent/start');
|
|
53
61
|
attachAudioSocket(client, {
|
|
54
62
|
emitMic: (pcm16) => { ctx.emit('realtime-agent/mic', pcm16); },
|
|
55
63
|
subscribeAudio: (listener) => ctx.on('realtime-agent/audio', listener),
|
|
56
64
|
maxFrameBytes: config.maxFrameBytes,
|
|
57
|
-
onDetach: () => {
|
|
65
|
+
onDetach: () => {
|
|
66
|
+
clients.delete(client);
|
|
67
|
+
// The last client leaving ends the session. This also covers the transport's own disposal,
|
|
68
|
+
// which terminates its clients — so a profile reload does not leave a session nobody holds.
|
|
69
|
+
if (config.openSessionOnConnect && clients.size === 0)
|
|
70
|
+
ctx.emit('realtime-agent/stop');
|
|
71
|
+
},
|
|
58
72
|
});
|
|
59
73
|
});
|
|
60
74
|
},
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAM7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACnE,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,GAGb,MAAM,YAAY,CAAA;AAEnB,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAA8B,MAAM,aAAa,CAAA;AACpF,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAwB,MAAM,cAAc,CAAA;AAEzF,mBAAmB;AACnB,MAAM,CAAC,MAAM,IAAI,GAAG,mBAAmB,CAAA;AAEvC,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,YAAY,CAAC;IAC3C,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAAC;IAChE,cAAc,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const DEFAULT_PATH = "/dsh-realtime/audio";
|
|
|
17
17
|
export declare const DEFAULT_MAX_FRAME_BYTES = 480000;
|
|
18
18
|
/** How many sockets may be attached at once. One microphone is the honest default. */
|
|
19
19
|
export declare const DEFAULT_MAX_CONNECTIONS = 1;
|
|
20
|
+
/** Whether an authenticated connection asks the agent to open the voice session. See the config field. */
|
|
21
|
+
export declare const DEFAULT_OPEN_SESSION_ON_CONNECT = true;
|
|
20
22
|
export interface RealtimeAudioWsConfig {
|
|
21
23
|
/**
|
|
22
24
|
* Absolute pathname to claim. Registered exactly, so it must be distinct from every other route in the
|
|
@@ -33,6 +35,16 @@ export interface RealtimeAudioWsConfig {
|
|
|
33
35
|
* two microphones on one session is a fault, not a feature.
|
|
34
36
|
*/
|
|
35
37
|
readonly maxConnections: number;
|
|
38
|
+
/**
|
|
39
|
+
* Ask the agent to open the voice session when an authenticated client connects, and to close it when the
|
|
40
|
+
* last one leaves. Default true.
|
|
41
|
+
*
|
|
42
|
+
* This is not the agent's `autoStart`, and the two defaults differ for a reason. `autoStart` opens a
|
|
43
|
+
* session at boot with nobody asking, which is why it ships false. This one responds to a connection,
|
|
44
|
+
* which takes an explicit authenticated action — and with it false, a working microphone produces silence
|
|
45
|
+
* that looks like a fault anywhere but in this file.
|
|
46
|
+
*/
|
|
47
|
+
readonly openSessionOnConnect: boolean;
|
|
36
48
|
}
|
|
37
49
|
/**
|
|
38
50
|
* The slice of a WebSocket this package uses, described structurally.
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0GAA0G;AAC1G,eAAO,MAAM,YAAY,wBAAwB,CAAA;AAEjD,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,SAAU,CAAA;AAE9C,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0GAA0G;AAC1G,eAAO,MAAM,YAAY,wBAAwB,CAAA;AAEjD,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,SAAU,CAAA;AAE9C,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC,0GAA0G;AAC1G,eAAO,MAAM,+BAA+B,OAAO,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAA;CACvC;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;IAC5B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C,SAAS,IAAI,IAAI,CAAA;IACjB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAA;IACnF,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAA;CAC9E"}
|
package/lib/types.js
CHANGED
|
@@ -17,4 +17,6 @@ export const DEFAULT_PATH = '/dsh-realtime/audio';
|
|
|
17
17
|
export const DEFAULT_MAX_FRAME_BYTES = 480_000;
|
|
18
18
|
/** How many sockets may be attached at once. One microphone is the honest default. */
|
|
19
19
|
export const DEFAULT_MAX_CONNECTIONS = 1;
|
|
20
|
+
/** Whether an authenticated connection asks the agent to open the voice session. See the config field. */
|
|
21
|
+
export const DEFAULT_OPEN_SESSION_ON_CONNECT = true;
|
|
20
22
|
//# sourceMappingURL=types.js.map
|
package/lib/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0GAA0G;AAC1G,MAAM,CAAC,MAAM,YAAY,GAAG,qBAAqB,CAAA;AAEjD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAA;AAE9C,sFAAsF;AACtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAA"}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,0GAA0G;AAC1G,MAAM,CAAC,MAAM,YAAY,GAAG,qBAAqB,CAAA;AAEjD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,uBAAuB,GAAG,OAAO,CAAA;AAE9C,sFAAsF;AACtF,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAA;AAExC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,+BAA+B,GAAG,IAAI,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-realtime-audio-ws",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The host end of the client half's transport: a WebSocket upgrade route that bridges microphone audio in and agent speech out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"ws": "^8.22.0",
|
|
55
|
-
"dsh-realtime-agent": "^0.2.
|
|
55
|
+
"dsh-realtime-agent": "^0.2.2"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@deepseek-ai/cordis": "^4.0.3",
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { createUpgradeAcceptor, rejectUpgrade } from './upgrade.ts'
|
|
|
22
22
|
import {
|
|
23
23
|
DEFAULT_MAX_CONNECTIONS,
|
|
24
24
|
DEFAULT_MAX_FRAME_BYTES,
|
|
25
|
+
DEFAULT_OPEN_SESSION_ON_CONNECT,
|
|
25
26
|
DEFAULT_PATH,
|
|
26
27
|
type AudioSocket,
|
|
27
28
|
type RealtimeAudioWsConfig,
|
|
@@ -38,6 +39,10 @@ export const Config = Schema.object({
|
|
|
38
39
|
path: Schema.string().default(DEFAULT_PATH),
|
|
39
40
|
maxFrameBytes: Schema.natural().default(DEFAULT_MAX_FRAME_BYTES),
|
|
40
41
|
maxConnections: Schema.natural().default(DEFAULT_MAX_CONNECTIONS),
|
|
42
|
+
// True, and deliberately not the same decision as the agent's `autoStart`. That one opens a session at
|
|
43
|
+
// boot with nobody asking, which is why it is false. This one opens a session because someone connected a
|
|
44
|
+
// microphone: a connection takes an explicit action, an authenticated one, and its absence is silence.
|
|
45
|
+
openSessionOnConnect: Schema.boolean().default(DEFAULT_OPEN_SESSION_ON_CONNECT),
|
|
41
46
|
})
|
|
42
47
|
|
|
43
48
|
/**
|
|
@@ -86,11 +91,19 @@ export function apply(ctx: Context, config: RealtimeAudioWsConfig): void {
|
|
|
86
91
|
return
|
|
87
92
|
}
|
|
88
93
|
clients.add(client)
|
|
94
|
+
// Ask for the session before the bridge goes in, so the first frames are written into a session
|
|
95
|
+
// that is being opened rather than dropped by the mic seam's no-session rule.
|
|
96
|
+
if (config.openSessionOnConnect) ctx.emit('realtime-agent/start')
|
|
89
97
|
attachAudioSocket(client, {
|
|
90
98
|
emitMic: (pcm16) => { ctx.emit('realtime-agent/mic', pcm16) },
|
|
91
99
|
subscribeAudio: (listener) => ctx.on('realtime-agent/audio', listener),
|
|
92
100
|
maxFrameBytes: config.maxFrameBytes,
|
|
93
|
-
onDetach: () => {
|
|
101
|
+
onDetach: () => {
|
|
102
|
+
clients.delete(client)
|
|
103
|
+
// The last client leaving ends the session. This also covers the transport's own disposal,
|
|
104
|
+
// which terminates its clients — so a profile reload does not leave a session nobody holds.
|
|
105
|
+
if (config.openSessionOnConnect && clients.size === 0) ctx.emit('realtime-agent/stop')
|
|
106
|
+
},
|
|
94
107
|
})
|
|
95
108
|
})
|
|
96
109
|
},
|
package/src/types.ts
CHANGED
|
@@ -21,6 +21,9 @@ export const DEFAULT_MAX_FRAME_BYTES = 480_000
|
|
|
21
21
|
/** How many sockets may be attached at once. One microphone is the honest default. */
|
|
22
22
|
export const DEFAULT_MAX_CONNECTIONS = 1
|
|
23
23
|
|
|
24
|
+
/** Whether an authenticated connection asks the agent to open the voice session. See the config field. */
|
|
25
|
+
export const DEFAULT_OPEN_SESSION_ON_CONNECT = true
|
|
26
|
+
|
|
24
27
|
export interface RealtimeAudioWsConfig {
|
|
25
28
|
/**
|
|
26
29
|
* Absolute pathname to claim. Registered exactly, so it must be distinct from every other route in the
|
|
@@ -37,6 +40,16 @@ export interface RealtimeAudioWsConfig {
|
|
|
37
40
|
* two microphones on one session is a fault, not a feature.
|
|
38
41
|
*/
|
|
39
42
|
readonly maxConnections: number
|
|
43
|
+
/**
|
|
44
|
+
* Ask the agent to open the voice session when an authenticated client connects, and to close it when the
|
|
45
|
+
* last one leaves. Default true.
|
|
46
|
+
*
|
|
47
|
+
* This is not the agent's `autoStart`, and the two defaults differ for a reason. `autoStart` opens a
|
|
48
|
+
* session at boot with nobody asking, which is why it ships false. This one responds to a connection,
|
|
49
|
+
* which takes an explicit authenticated action — and with it false, a working microphone produces silence
|
|
50
|
+
* that looks like a fault anywhere but in this file.
|
|
51
|
+
*/
|
|
52
|
+
readonly openSessionOnConnect: boolean
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
/**
|