@stimm/protocol 0.1.0
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/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +81 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +11 -0
- package/dist/messages.js.map +1 -0
- package/dist/supervisor-client.d.ts +75 -0
- package/dist/supervisor-client.d.ts.map +1 -0
- package/dist/supervisor-client.js +123 -0
- package/dist/supervisor-client.js.map +1 -0
- package/package.json +36 -0
- package/src/index.ts +31 -0
- package/src/messages.ts +115 -0
- package/src/supervisor-client.ts +193 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stimm/protocol — TypeScript types and supervisor client
|
|
3
|
+
* for Stimm dual-agent voice orchestration.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export type { TranscriptMessage, StateMessage, BeforeSpeakMessage, MetricsMessage, InstructionMessage, ContextMessage, ActionResultMessage, ModeMessage, OverrideMessage, AgentMode, VoiceAgentMessage, SupervisorMessage, StimmMessage, } from "./messages.js";
|
|
8
|
+
export { STIMM_TOPIC } from "./messages.js";
|
|
9
|
+
export { StimmSupervisorClient, type StimmSupervisorClientOptions, } from "./supervisor-client.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,YAAY,EACV,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,GACb,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,GAClC,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stimm/protocol — TypeScript types and supervisor client
|
|
3
|
+
* for Stimm dual-agent voice orchestration.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
export { STIMM_TOPIC } from "./messages.js";
|
|
8
|
+
// Supervisor client
|
|
9
|
+
export { StimmSupervisorClient, } from "./supervisor-client.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmBH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,oBAAoB;AACpB,OAAO,EACL,qBAAqB,GAEtB,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stimm Protocol — TypeScript message types.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of the Python Pydantic models in `stimm.protocol`.
|
|
5
|
+
* All messages are exchanged as JSON over LiveKit reliable data channels.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Real-time speech transcript from the voice agent's STT. */
|
|
10
|
+
export interface TranscriptMessage {
|
|
11
|
+
type: "transcript";
|
|
12
|
+
/** Whether this is a partial (streaming) or final transcript. */
|
|
13
|
+
partial: boolean;
|
|
14
|
+
text: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
confidence: number;
|
|
17
|
+
}
|
|
18
|
+
/** Voice agent state transition. */
|
|
19
|
+
export interface StateMessage {
|
|
20
|
+
type: "state";
|
|
21
|
+
state: "listening" | "thinking" | "speaking";
|
|
22
|
+
timestamp: number;
|
|
23
|
+
}
|
|
24
|
+
/** Emitted before the voice agent sends text to TTS. */
|
|
25
|
+
export interface BeforeSpeakMessage {
|
|
26
|
+
type: "before_speak";
|
|
27
|
+
text: string;
|
|
28
|
+
turn_id: string;
|
|
29
|
+
}
|
|
30
|
+
/** Per-turn latency metrics. */
|
|
31
|
+
export interface MetricsMessage {
|
|
32
|
+
type: "metrics";
|
|
33
|
+
turn: number;
|
|
34
|
+
vad_ms: number;
|
|
35
|
+
stt_ms: number;
|
|
36
|
+
llm_ttft_ms: number;
|
|
37
|
+
tts_ttfb_ms: number;
|
|
38
|
+
total_ms: number;
|
|
39
|
+
}
|
|
40
|
+
/** Voice agent operating mode. */
|
|
41
|
+
export type AgentMode = "autonomous" | "relay" | "hybrid";
|
|
42
|
+
/** Instruction for the voice agent to speak or incorporate. */
|
|
43
|
+
export interface InstructionMessage {
|
|
44
|
+
type: "instruction";
|
|
45
|
+
text: string;
|
|
46
|
+
priority: "normal" | "interrupt";
|
|
47
|
+
speak: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Additional context for the voice agent's working memory. */
|
|
50
|
+
export interface ContextMessage {
|
|
51
|
+
type: "context";
|
|
52
|
+
text: string;
|
|
53
|
+
append: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** Notification that a tool/action completed in the supervisor. */
|
|
56
|
+
export interface ActionResultMessage {
|
|
57
|
+
type: "action_result";
|
|
58
|
+
action: string;
|
|
59
|
+
status: string;
|
|
60
|
+
summary: string;
|
|
61
|
+
}
|
|
62
|
+
/** Switch the voice agent's operating mode. */
|
|
63
|
+
export interface ModeMessage {
|
|
64
|
+
type: "mode";
|
|
65
|
+
mode: AgentMode;
|
|
66
|
+
}
|
|
67
|
+
/** Cancel the voice agent's pending response and replace it. */
|
|
68
|
+
export interface OverrideMessage {
|
|
69
|
+
type: "override";
|
|
70
|
+
turn_id: string;
|
|
71
|
+
replacement: string;
|
|
72
|
+
}
|
|
73
|
+
/** Any message sent by the voice agent. */
|
|
74
|
+
export type VoiceAgentMessage = TranscriptMessage | StateMessage | BeforeSpeakMessage | MetricsMessage;
|
|
75
|
+
/** Any message sent by the supervisor. */
|
|
76
|
+
export type SupervisorMessage = InstructionMessage | ContextMessage | ActionResultMessage | ModeMessage | OverrideMessage;
|
|
77
|
+
/** Any stimm protocol message. */
|
|
78
|
+
export type StimmMessage = VoiceAgentMessage | SupervisorMessage;
|
|
79
|
+
/** The LiveKit data channel topic used by stimm. */
|
|
80
|
+
export declare const STIMM_TOPIC = "stimm";
|
|
81
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,oCAAoC;AACpC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,UAAU,CAAC;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wDAAwD;AACxD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,kCAAkC;AAClC,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE1D,+DAA+D;AAC/D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAAC;IACjC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,mEAAmE;AACnE,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAMD,2CAA2C;AAC3C,MAAM,MAAM,iBAAiB,GACzB,iBAAiB,GACjB,YAAY,GACZ,kBAAkB,GAClB,cAAc,CAAC;AAEnB,0CAA0C;AAC1C,MAAM,MAAM,iBAAiB,GACzB,kBAAkB,GAClB,cAAc,GACd,mBAAmB,GACnB,WAAW,GACX,eAAe,CAAC;AAEpB,kCAAkC;AAClC,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAEjE,oDAAoD;AACpD,eAAO,MAAM,WAAW,UAAU,CAAC"}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stimm Protocol — TypeScript message types.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of the Python Pydantic models in `stimm.protocol`.
|
|
5
|
+
* All messages are exchanged as JSON over LiveKit reliable data channels.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** The LiveKit data channel topic used by stimm. */
|
|
10
|
+
export const STIMM_TOPIC = "stimm";
|
|
11
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0GH,oDAAoD;AACpD,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StimmSupervisorClient — TypeScript supervisor for Node.js consumers.
|
|
3
|
+
*
|
|
4
|
+
* Connects to a LiveKit room as a data-only participant and provides
|
|
5
|
+
* a typed event-driven interface for receiving voice agent messages
|
|
6
|
+
* and sending instructions back.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { StimmSupervisorClient } from "@stimm/protocol";
|
|
11
|
+
*
|
|
12
|
+
* const client = new StimmSupervisorClient({
|
|
13
|
+
* livekitUrl: "ws://localhost:7880",
|
|
14
|
+
* token: supervisorToken,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* client.on("transcript", async (msg) => {
|
|
18
|
+
* if (!msg.partial) {
|
|
19
|
+
* const result = await myAgent.process(msg.text);
|
|
20
|
+
* await client.instruct({ text: result, speak: true, priority: "normal" });
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* await client.connect();
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import type { AgentMode, TranscriptMessage, StateMessage, BeforeSpeakMessage, MetricsMessage, InstructionMessage, ContextMessage, ActionResultMessage, OverrideMessage } from "./messages.js";
|
|
30
|
+
type VoiceAgentEventMap = {
|
|
31
|
+
transcript: TranscriptMessage;
|
|
32
|
+
state: StateMessage;
|
|
33
|
+
before_speak: BeforeSpeakMessage;
|
|
34
|
+
metrics: MetricsMessage;
|
|
35
|
+
};
|
|
36
|
+
type VoiceAgentEvent = keyof VoiceAgentEventMap;
|
|
37
|
+
type EventHandler<T> = (msg: T) => void | Promise<void>;
|
|
38
|
+
export interface StimmSupervisorClientOptions {
|
|
39
|
+
/** LiveKit server WebSocket URL. */
|
|
40
|
+
livekitUrl: string;
|
|
41
|
+
/** Access token with data-channel permissions. */
|
|
42
|
+
token: string;
|
|
43
|
+
}
|
|
44
|
+
export declare class StimmSupervisorClient {
|
|
45
|
+
private room;
|
|
46
|
+
private url;
|
|
47
|
+
private token;
|
|
48
|
+
private handlers;
|
|
49
|
+
private _connected;
|
|
50
|
+
constructor(options: StimmSupervisorClientOptions);
|
|
51
|
+
/** Whether the client is currently connected. */
|
|
52
|
+
get connected(): boolean;
|
|
53
|
+
/** Connect to the LiveKit room. */
|
|
54
|
+
connect(): Promise<void>;
|
|
55
|
+
/** Disconnect from the room. */
|
|
56
|
+
disconnect(): Promise<void>;
|
|
57
|
+
/** Register a handler for voice agent events. */
|
|
58
|
+
on<E extends VoiceAgentEvent>(event: E, handler: EventHandler<VoiceAgentEventMap[E]>): void;
|
|
59
|
+
/** Remove a handler. */
|
|
60
|
+
off<E extends VoiceAgentEvent>(event: E, handler: EventHandler<VoiceAgentEventMap[E]>): void;
|
|
61
|
+
private onData;
|
|
62
|
+
private send;
|
|
63
|
+
/** Send an instruction to the voice agent. */
|
|
64
|
+
instruct(msg: Omit<InstructionMessage, "type">): Promise<void>;
|
|
65
|
+
/** Add context to the voice agent's working memory. */
|
|
66
|
+
addContext(msg: Omit<ContextMessage, "type">): Promise<void>;
|
|
67
|
+
/** Notify the voice agent that a tool/action completed. */
|
|
68
|
+
sendActionResult(msg: Omit<ActionResultMessage, "type">): Promise<void>;
|
|
69
|
+
/** Switch the voice agent's operating mode. */
|
|
70
|
+
setMode(mode: AgentMode): Promise<void>;
|
|
71
|
+
/** Cancel the voice agent's pending response and replace it. */
|
|
72
|
+
override(msg: Omit<OverrideMessage, "type">): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=supervisor-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor-client.d.ts","sourceRoot":"","sources":["../src/supervisor-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,KAAK,EACV,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EAEnB,eAAe,EAEhB,MAAM,eAAe,CAAC;AAQvB,KAAK,kBAAkB,GAAG;IACxB,UAAU,EAAE,iBAAiB,CAAC;IAC9B,KAAK,EAAE,YAAY,CAAC;IACpB,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,KAAK,eAAe,GAAG,MAAM,kBAAkB,CAAC;AAEhD,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAMxD,MAAM,WAAW,4BAA4B;IAC3C,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAoD;IACpE,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,EAAE,4BAA4B;IAMjD,iDAAiD;IACjD,IAAI,SAAS,IAAI,OAAO,CAEvB;IAID,mCAAmC;IAC7B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,gCAAgC;IAC1B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC,iDAAiD;IACjD,EAAE,CAAC,CAAC,SAAS,eAAe,EAC1B,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAC3C,IAAI;IAMP,wBAAwB;IACxB,GAAG,CAAC,CAAC,SAAS,eAAe,EAC3B,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,YAAY,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAC3C,IAAI;IAOP,OAAO,CAAC,MAAM;YAwBA,IAAI;IAQlB,8CAA8C;IACxC,QAAQ,CACZ,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,GACpC,OAAO,CAAC,IAAI,CAAC;IAIhB,uDAAuD;IACjD,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE,2DAA2D;IACrD,gBAAgB,CACpB,GAAG,EAAE,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC;IAIhB,+CAA+C;IACzC,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,gEAAgE;IAC1D,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;CAGlE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StimmSupervisorClient — TypeScript supervisor for Node.js consumers.
|
|
3
|
+
*
|
|
4
|
+
* Connects to a LiveKit room as a data-only participant and provides
|
|
5
|
+
* a typed event-driven interface for receiving voice agent messages
|
|
6
|
+
* and sending instructions back.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { StimmSupervisorClient } from "@stimm/protocol";
|
|
11
|
+
*
|
|
12
|
+
* const client = new StimmSupervisorClient({
|
|
13
|
+
* livekitUrl: "ws://localhost:7880",
|
|
14
|
+
* token: supervisorToken,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* client.on("transcript", async (msg) => {
|
|
18
|
+
* if (!msg.partial) {
|
|
19
|
+
* const result = await myAgent.process(msg.text);
|
|
20
|
+
* await client.instruct({ text: result, speak: true, priority: "normal" });
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* await client.connect();
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
import { Room, RoomEvent } from "livekit-client";
|
|
30
|
+
import { STIMM_TOPIC } from "./messages.js";
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Client
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
export class StimmSupervisorClient {
|
|
35
|
+
room;
|
|
36
|
+
url;
|
|
37
|
+
token;
|
|
38
|
+
handlers = new Map();
|
|
39
|
+
_connected = false;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.url = options.livekitUrl;
|
|
42
|
+
this.token = options.token;
|
|
43
|
+
this.room = new Room();
|
|
44
|
+
}
|
|
45
|
+
/** Whether the client is currently connected. */
|
|
46
|
+
get connected() {
|
|
47
|
+
return this._connected;
|
|
48
|
+
}
|
|
49
|
+
// -- Connection -----------------------------------------------------------
|
|
50
|
+
/** Connect to the LiveKit room. */
|
|
51
|
+
async connect() {
|
|
52
|
+
this.room.on(RoomEvent.DataReceived, this.onData.bind(this));
|
|
53
|
+
await this.room.connect(this.url, this.token);
|
|
54
|
+
this._connected = true;
|
|
55
|
+
}
|
|
56
|
+
/** Disconnect from the room. */
|
|
57
|
+
async disconnect() {
|
|
58
|
+
await this.room.disconnect();
|
|
59
|
+
this._connected = false;
|
|
60
|
+
}
|
|
61
|
+
// -- Receiving messages ---------------------------------------------------
|
|
62
|
+
/** Register a handler for voice agent events. */
|
|
63
|
+
on(event, handler) {
|
|
64
|
+
const list = this.handlers.get(event) ?? [];
|
|
65
|
+
list.push(handler);
|
|
66
|
+
this.handlers.set(event, list);
|
|
67
|
+
}
|
|
68
|
+
/** Remove a handler. */
|
|
69
|
+
off(event, handler) {
|
|
70
|
+
const list = this.handlers.get(event);
|
|
71
|
+
if (!list)
|
|
72
|
+
return;
|
|
73
|
+
const idx = list.indexOf(handler);
|
|
74
|
+
if (idx >= 0)
|
|
75
|
+
list.splice(idx, 1);
|
|
76
|
+
}
|
|
77
|
+
onData(payload, participant, kind, topic) {
|
|
78
|
+
if (topic !== STIMM_TOPIC)
|
|
79
|
+
return;
|
|
80
|
+
try {
|
|
81
|
+
const text = new TextDecoder().decode(payload);
|
|
82
|
+
const msg = JSON.parse(text);
|
|
83
|
+
const handlers = this.handlers.get(msg.type) ?? [];
|
|
84
|
+
for (const handler of handlers) {
|
|
85
|
+
Promise.resolve(handler(msg)).catch((err) => {
|
|
86
|
+
console.error(`[stimm] Error in ${msg.type} handler:`, err);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error("[stimm] Failed to deserialize message:", err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// -- Sending messages to voice agent --------------------------------------
|
|
95
|
+
async send(msg) {
|
|
96
|
+
const payload = new TextEncoder().encode(JSON.stringify(msg));
|
|
97
|
+
await this.room.localParticipant.publishData(payload, {
|
|
98
|
+
topic: STIMM_TOPIC,
|
|
99
|
+
reliable: true,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Send an instruction to the voice agent. */
|
|
103
|
+
async instruct(msg) {
|
|
104
|
+
await this.send({ type: "instruction", ...msg });
|
|
105
|
+
}
|
|
106
|
+
/** Add context to the voice agent's working memory. */
|
|
107
|
+
async addContext(msg) {
|
|
108
|
+
await this.send({ type: "context", ...msg });
|
|
109
|
+
}
|
|
110
|
+
/** Notify the voice agent that a tool/action completed. */
|
|
111
|
+
async sendActionResult(msg) {
|
|
112
|
+
await this.send({ type: "action_result", ...msg });
|
|
113
|
+
}
|
|
114
|
+
/** Switch the voice agent's operating mode. */
|
|
115
|
+
async setMode(mode) {
|
|
116
|
+
await this.send({ type: "mode", mode });
|
|
117
|
+
}
|
|
118
|
+
/** Cancel the voice agent's pending response and replace it. */
|
|
119
|
+
async override(msg) {
|
|
120
|
+
await this.send({ type: "override", ...msg });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=supervisor-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor-client.js","sourceRoot":"","sources":["../src/supervisor-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stimm/protocol",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript types and supervisor client for Stimm dual-agent voice orchestration",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"check": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"livekit-client": "^2.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.4"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/etienne/stimm",
|
|
27
|
+
"directory": "packages/protocol-ts"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"stimm",
|
|
31
|
+
"voice",
|
|
32
|
+
"livekit",
|
|
33
|
+
"dual-agent",
|
|
34
|
+
"protocol"
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stimm/protocol — TypeScript types and supervisor client
|
|
3
|
+
* for Stimm dual-agent voice orchestration.
|
|
4
|
+
*
|
|
5
|
+
* @packageDocumentation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Message types
|
|
9
|
+
export type {
|
|
10
|
+
TranscriptMessage,
|
|
11
|
+
StateMessage,
|
|
12
|
+
BeforeSpeakMessage,
|
|
13
|
+
MetricsMessage,
|
|
14
|
+
InstructionMessage,
|
|
15
|
+
ContextMessage,
|
|
16
|
+
ActionResultMessage,
|
|
17
|
+
ModeMessage,
|
|
18
|
+
OverrideMessage,
|
|
19
|
+
AgentMode,
|
|
20
|
+
VoiceAgentMessage,
|
|
21
|
+
SupervisorMessage,
|
|
22
|
+
StimmMessage,
|
|
23
|
+
} from "./messages.js";
|
|
24
|
+
|
|
25
|
+
export { STIMM_TOPIC } from "./messages.js";
|
|
26
|
+
|
|
27
|
+
// Supervisor client
|
|
28
|
+
export {
|
|
29
|
+
StimmSupervisorClient,
|
|
30
|
+
type StimmSupervisorClientOptions,
|
|
31
|
+
} from "./supervisor-client.js";
|
package/src/messages.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stimm Protocol — TypeScript message types.
|
|
3
|
+
*
|
|
4
|
+
* Mirror of the Python Pydantic models in `stimm.protocol`.
|
|
5
|
+
* All messages are exchanged as JSON over LiveKit reliable data channels.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Voice Agent → Supervisor
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
/** Real-time speech transcript from the voice agent's STT. */
|
|
15
|
+
export interface TranscriptMessage {
|
|
16
|
+
type: "transcript";
|
|
17
|
+
/** Whether this is a partial (streaming) or final transcript. */
|
|
18
|
+
partial: boolean;
|
|
19
|
+
text: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
confidence: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Voice agent state transition. */
|
|
25
|
+
export interface StateMessage {
|
|
26
|
+
type: "state";
|
|
27
|
+
state: "listening" | "thinking" | "speaking";
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Emitted before the voice agent sends text to TTS. */
|
|
32
|
+
export interface BeforeSpeakMessage {
|
|
33
|
+
type: "before_speak";
|
|
34
|
+
text: string;
|
|
35
|
+
turn_id: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Per-turn latency metrics. */
|
|
39
|
+
export interface MetricsMessage {
|
|
40
|
+
type: "metrics";
|
|
41
|
+
turn: number;
|
|
42
|
+
vad_ms: number;
|
|
43
|
+
stt_ms: number;
|
|
44
|
+
llm_ttft_ms: number;
|
|
45
|
+
tts_ttfb_ms: number;
|
|
46
|
+
total_ms: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Supervisor → Voice Agent
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/** Voice agent operating mode. */
|
|
54
|
+
export type AgentMode = "autonomous" | "relay" | "hybrid";
|
|
55
|
+
|
|
56
|
+
/** Instruction for the voice agent to speak or incorporate. */
|
|
57
|
+
export interface InstructionMessage {
|
|
58
|
+
type: "instruction";
|
|
59
|
+
text: string;
|
|
60
|
+
priority: "normal" | "interrupt";
|
|
61
|
+
speak: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Additional context for the voice agent's working memory. */
|
|
65
|
+
export interface ContextMessage {
|
|
66
|
+
type: "context";
|
|
67
|
+
text: string;
|
|
68
|
+
append: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Notification that a tool/action completed in the supervisor. */
|
|
72
|
+
export interface ActionResultMessage {
|
|
73
|
+
type: "action_result";
|
|
74
|
+
action: string;
|
|
75
|
+
status: string;
|
|
76
|
+
summary: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Switch the voice agent's operating mode. */
|
|
80
|
+
export interface ModeMessage {
|
|
81
|
+
type: "mode";
|
|
82
|
+
mode: AgentMode;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Cancel the voice agent's pending response and replace it. */
|
|
86
|
+
export interface OverrideMessage {
|
|
87
|
+
type: "override";
|
|
88
|
+
turn_id: string;
|
|
89
|
+
replacement: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Union types
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/** Any message sent by the voice agent. */
|
|
97
|
+
export type VoiceAgentMessage =
|
|
98
|
+
| TranscriptMessage
|
|
99
|
+
| StateMessage
|
|
100
|
+
| BeforeSpeakMessage
|
|
101
|
+
| MetricsMessage;
|
|
102
|
+
|
|
103
|
+
/** Any message sent by the supervisor. */
|
|
104
|
+
export type SupervisorMessage =
|
|
105
|
+
| InstructionMessage
|
|
106
|
+
| ContextMessage
|
|
107
|
+
| ActionResultMessage
|
|
108
|
+
| ModeMessage
|
|
109
|
+
| OverrideMessage;
|
|
110
|
+
|
|
111
|
+
/** Any stimm protocol message. */
|
|
112
|
+
export type StimmMessage = VoiceAgentMessage | SupervisorMessage;
|
|
113
|
+
|
|
114
|
+
/** The LiveKit data channel topic used by stimm. */
|
|
115
|
+
export const STIMM_TOPIC = "stimm";
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StimmSupervisorClient — TypeScript supervisor for Node.js consumers.
|
|
3
|
+
*
|
|
4
|
+
* Connects to a LiveKit room as a data-only participant and provides
|
|
5
|
+
* a typed event-driven interface for receiving voice agent messages
|
|
6
|
+
* and sending instructions back.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { StimmSupervisorClient } from "@stimm/protocol";
|
|
11
|
+
*
|
|
12
|
+
* const client = new StimmSupervisorClient({
|
|
13
|
+
* livekitUrl: "ws://localhost:7880",
|
|
14
|
+
* token: supervisorToken,
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* client.on("transcript", async (msg) => {
|
|
18
|
+
* if (!msg.partial) {
|
|
19
|
+
* const result = await myAgent.process(msg.text);
|
|
20
|
+
* await client.instruct({ text: result, speak: true, priority: "normal" });
|
|
21
|
+
* }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* await client.connect();
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { Room, DataPacket_Kind, RoomEvent } from "livekit-client";
|
|
31
|
+
import type {
|
|
32
|
+
AgentMode,
|
|
33
|
+
TranscriptMessage,
|
|
34
|
+
StateMessage,
|
|
35
|
+
BeforeSpeakMessage,
|
|
36
|
+
MetricsMessage,
|
|
37
|
+
InstructionMessage,
|
|
38
|
+
ContextMessage,
|
|
39
|
+
ActionResultMessage,
|
|
40
|
+
ModeMessage,
|
|
41
|
+
OverrideMessage,
|
|
42
|
+
StimmMessage,
|
|
43
|
+
} from "./messages.js";
|
|
44
|
+
|
|
45
|
+
import { STIMM_TOPIC } from "./messages.js";
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Event types
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
type VoiceAgentEventMap = {
|
|
52
|
+
transcript: TranscriptMessage;
|
|
53
|
+
state: StateMessage;
|
|
54
|
+
before_speak: BeforeSpeakMessage;
|
|
55
|
+
metrics: MetricsMessage;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
type VoiceAgentEvent = keyof VoiceAgentEventMap;
|
|
59
|
+
|
|
60
|
+
type EventHandler<T> = (msg: T) => void | Promise<void>;
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Options
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
export interface StimmSupervisorClientOptions {
|
|
67
|
+
/** LiveKit server WebSocket URL. */
|
|
68
|
+
livekitUrl: string;
|
|
69
|
+
/** Access token with data-channel permissions. */
|
|
70
|
+
token: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Client
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
export class StimmSupervisorClient {
|
|
78
|
+
private room: Room;
|
|
79
|
+
private url: string;
|
|
80
|
+
private token: string;
|
|
81
|
+
private handlers: Map<string, Array<EventHandler<any>>> = new Map();
|
|
82
|
+
private _connected = false;
|
|
83
|
+
|
|
84
|
+
constructor(options: StimmSupervisorClientOptions) {
|
|
85
|
+
this.url = options.livekitUrl;
|
|
86
|
+
this.token = options.token;
|
|
87
|
+
this.room = new Room();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Whether the client is currently connected. */
|
|
91
|
+
get connected(): boolean {
|
|
92
|
+
return this._connected;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// -- Connection -----------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/** Connect to the LiveKit room. */
|
|
98
|
+
async connect(): Promise<void> {
|
|
99
|
+
this.room.on(RoomEvent.DataReceived, this.onData.bind(this));
|
|
100
|
+
await this.room.connect(this.url, this.token);
|
|
101
|
+
this._connected = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Disconnect from the room. */
|
|
105
|
+
async disconnect(): Promise<void> {
|
|
106
|
+
await this.room.disconnect();
|
|
107
|
+
this._connected = false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// -- Receiving messages ---------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/** Register a handler for voice agent events. */
|
|
113
|
+
on<E extends VoiceAgentEvent>(
|
|
114
|
+
event: E,
|
|
115
|
+
handler: EventHandler<VoiceAgentEventMap[E]>,
|
|
116
|
+
): void {
|
|
117
|
+
const list = this.handlers.get(event) ?? [];
|
|
118
|
+
list.push(handler as EventHandler<any>);
|
|
119
|
+
this.handlers.set(event, list);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Remove a handler. */
|
|
123
|
+
off<E extends VoiceAgentEvent>(
|
|
124
|
+
event: E,
|
|
125
|
+
handler: EventHandler<VoiceAgentEventMap[E]>,
|
|
126
|
+
): void {
|
|
127
|
+
const list = this.handlers.get(event);
|
|
128
|
+
if (!list) return;
|
|
129
|
+
const idx = list.indexOf(handler as EventHandler<any>);
|
|
130
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private onData(
|
|
134
|
+
payload: Uint8Array,
|
|
135
|
+
participant?: unknown,
|
|
136
|
+
kind?: DataPacket_Kind,
|
|
137
|
+
topic?: string,
|
|
138
|
+
): void {
|
|
139
|
+
if (topic !== STIMM_TOPIC) return;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const text = new TextDecoder().decode(payload);
|
|
143
|
+
const msg = JSON.parse(text) as StimmMessage;
|
|
144
|
+
const handlers = this.handlers.get(msg.type) ?? [];
|
|
145
|
+
for (const handler of handlers) {
|
|
146
|
+
Promise.resolve(handler(msg)).catch((err) => {
|
|
147
|
+
console.error(`[stimm] Error in ${msg.type} handler:`, err);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error("[stimm] Failed to deserialize message:", err);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// -- Sending messages to voice agent --------------------------------------
|
|
156
|
+
|
|
157
|
+
private async send(msg: Record<string, unknown>): Promise<void> {
|
|
158
|
+
const payload = new TextEncoder().encode(JSON.stringify(msg));
|
|
159
|
+
await this.room.localParticipant.publishData(payload, {
|
|
160
|
+
topic: STIMM_TOPIC,
|
|
161
|
+
reliable: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Send an instruction to the voice agent. */
|
|
166
|
+
async instruct(
|
|
167
|
+
msg: Omit<InstructionMessage, "type">,
|
|
168
|
+
): Promise<void> {
|
|
169
|
+
await this.send({ type: "instruction", ...msg });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Add context to the voice agent's working memory. */
|
|
173
|
+
async addContext(msg: Omit<ContextMessage, "type">): Promise<void> {
|
|
174
|
+
await this.send({ type: "context", ...msg });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Notify the voice agent that a tool/action completed. */
|
|
178
|
+
async sendActionResult(
|
|
179
|
+
msg: Omit<ActionResultMessage, "type">,
|
|
180
|
+
): Promise<void> {
|
|
181
|
+
await this.send({ type: "action_result", ...msg });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Switch the voice agent's operating mode. */
|
|
185
|
+
async setMode(mode: AgentMode): Promise<void> {
|
|
186
|
+
await this.send({ type: "mode", mode });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Cancel the voice agent's pending response and replace it. */
|
|
190
|
+
async override(msg: Omit<OverrideMessage, "type">): Promise<void> {
|
|
191
|
+
await this.send({ type: "override", ...msg });
|
|
192
|
+
}
|
|
193
|
+
}
|