felona-voice 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.
Files changed (126) hide show
  1. package/dist/agent.d.ts +105 -0
  2. package/dist/agent.d.ts.map +1 -0
  3. package/dist/agent.js +287 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/analytics/index.d.ts +3 -0
  6. package/dist/analytics/index.d.ts.map +1 -0
  7. package/dist/analytics/index.js +2 -0
  8. package/dist/analytics/index.js.map +1 -0
  9. package/dist/analytics/logger.d.ts +92 -0
  10. package/dist/analytics/logger.d.ts.map +1 -0
  11. package/dist/analytics/logger.js +75 -0
  12. package/dist/analytics/logger.js.map +1 -0
  13. package/dist/index.d.ts +45 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +54 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/jev/action-space.d.ts +38 -0
  18. package/dist/jev/action-space.d.ts.map +1 -0
  19. package/dist/jev/action-space.js +92 -0
  20. package/dist/jev/action-space.js.map +1 -0
  21. package/dist/jev/embeddings.d.ts +23 -0
  22. package/dist/jev/embeddings.d.ts.map +1 -0
  23. package/dist/jev/embeddings.js +63 -0
  24. package/dist/jev/embeddings.js.map +1 -0
  25. package/dist/jev/engine.d.ts +101 -0
  26. package/dist/jev/engine.d.ts.map +1 -0
  27. package/dist/jev/engine.js +173 -0
  28. package/dist/jev/engine.js.map +1 -0
  29. package/dist/jev/index.d.ts +4 -0
  30. package/dist/jev/index.d.ts.map +1 -0
  31. package/dist/jev/index.js +4 -0
  32. package/dist/jev/index.js.map +1 -0
  33. package/dist/llm/index.d.ts +2 -0
  34. package/dist/llm/index.d.ts.map +1 -0
  35. package/dist/llm/index.js +2 -0
  36. package/dist/llm/index.js.map +1 -0
  37. package/dist/llm/openai.d.ts +22 -0
  38. package/dist/llm/openai.d.ts.map +1 -0
  39. package/dist/llm/openai.js +140 -0
  40. package/dist/llm/openai.js.map +1 -0
  41. package/dist/memory/context.d.ts +53 -0
  42. package/dist/memory/context.d.ts.map +1 -0
  43. package/dist/memory/context.js +95 -0
  44. package/dist/memory/context.js.map +1 -0
  45. package/dist/memory/index.d.ts +2 -0
  46. package/dist/memory/index.d.ts.map +1 -0
  47. package/dist/memory/index.js +2 -0
  48. package/dist/memory/index.js.map +1 -0
  49. package/dist/pipeline.d.ts +93 -0
  50. package/dist/pipeline.d.ts.map +1 -0
  51. package/dist/pipeline.js +255 -0
  52. package/dist/pipeline.js.map +1 -0
  53. package/dist/stt/deepgram.d.ts +27 -0
  54. package/dist/stt/deepgram.d.ts.map +1 -0
  55. package/dist/stt/deepgram.js +137 -0
  56. package/dist/stt/deepgram.js.map +1 -0
  57. package/dist/stt/index.d.ts +2 -0
  58. package/dist/stt/index.d.ts.map +1 -0
  59. package/dist/stt/index.js +2 -0
  60. package/dist/stt/index.js.map +1 -0
  61. package/dist/tools/index.d.ts +2 -0
  62. package/dist/tools/index.d.ts.map +1 -0
  63. package/dist/tools/index.js +2 -0
  64. package/dist/tools/index.js.map +1 -0
  65. package/dist/tools/registry.d.ts +38 -0
  66. package/dist/tools/registry.d.ts.map +1 -0
  67. package/dist/tools/registry.js +77 -0
  68. package/dist/tools/registry.js.map +1 -0
  69. package/dist/transport/index.d.ts +2 -0
  70. package/dist/transport/index.d.ts.map +1 -0
  71. package/dist/transport/index.js +2 -0
  72. package/dist/transport/index.js.map +1 -0
  73. package/dist/transport/websocket.d.ts +38 -0
  74. package/dist/transport/websocket.d.ts.map +1 -0
  75. package/dist/transport/websocket.js +180 -0
  76. package/dist/transport/websocket.js.map +1 -0
  77. package/dist/tts/eleven-labs.d.ts +27 -0
  78. package/dist/tts/eleven-labs.d.ts.map +1 -0
  79. package/dist/tts/eleven-labs.js +73 -0
  80. package/dist/tts/eleven-labs.js.map +1 -0
  81. package/dist/tts/index.d.ts +2 -0
  82. package/dist/tts/index.d.ts.map +1 -0
  83. package/dist/tts/index.js +2 -0
  84. package/dist/tts/index.js.map +1 -0
  85. package/dist/types.d.ts +370 -0
  86. package/dist/types.d.ts.map +1 -0
  87. package/dist/types.js +13 -0
  88. package/dist/types.js.map +1 -0
  89. package/dist/vad/energy.d.ts +49 -0
  90. package/dist/vad/energy.d.ts.map +1 -0
  91. package/dist/vad/energy.js +108 -0
  92. package/dist/vad/energy.js.map +1 -0
  93. package/dist/vad/index.d.ts +2 -0
  94. package/dist/vad/index.d.ts.map +1 -0
  95. package/dist/vad/index.js +2 -0
  96. package/dist/vad/index.js.map +1 -0
  97. package/package.json +51 -0
  98. package/src/agent.ts +373 -0
  99. package/src/analytics/index.ts +7 -0
  100. package/src/analytics/logger.ts +152 -0
  101. package/src/index.ts +121 -0
  102. package/src/jev/action-space.ts +119 -0
  103. package/src/jev/embeddings.ts +81 -0
  104. package/src/jev/engine.ts +225 -0
  105. package/src/jev/index.ts +3 -0
  106. package/src/llm/index.ts +1 -0
  107. package/src/llm/openai.ts +178 -0
  108. package/src/memory/context.ts +124 -0
  109. package/src/memory/index.ts +1 -0
  110. package/src/pipeline.ts +331 -0
  111. package/src/stt/deepgram.ts +182 -0
  112. package/src/stt/index.ts +1 -0
  113. package/src/tools/index.ts +1 -0
  114. package/src/tools/registry.ts +99 -0
  115. package/src/transport/index.ts +1 -0
  116. package/src/transport/websocket.ts +227 -0
  117. package/src/tts/eleven-labs.ts +100 -0
  118. package/src/tts/index.ts +1 -0
  119. package/src/types.ts +429 -0
  120. package/src/vad/energy.ts +135 -0
  121. package/src/vad/index.ts +1 -0
  122. package/tests/jev.test.ts +198 -0
  123. package/tests/memory.test.ts +169 -0
  124. package/tests/vad.test.ts +94 -0
  125. package/tsconfig.json +9 -0
  126. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * OpenAILLM — LLM provider using OpenAI's chat completions API.
3
+ *
4
+ * The LLM in Felona Voice is used for **content generation**, not flow control.
5
+ * JEV decides what to do; the LLM decides how to say it.
6
+ */
7
+ export class OpenAILLM {
8
+ name = "openai";
9
+ apiKey;
10
+ baseUrl;
11
+ constructor(options) {
12
+ this.apiKey = options.apiKey;
13
+ this.baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
14
+ }
15
+ create(options) {
16
+ return new OpenAILLMInterface({
17
+ apiKey: this.apiKey,
18
+ baseUrl: this.baseUrl,
19
+ model: options.model,
20
+ temperature: options.temperature ?? 0.7,
21
+ maxTokens: options.maxTokens ?? 256,
22
+ });
23
+ }
24
+ }
25
+ class OpenAILLMInterface {
26
+ apiKey;
27
+ baseUrl;
28
+ model;
29
+ temperature;
30
+ maxTokens;
31
+ constructor(options) {
32
+ this.apiKey = options.apiKey;
33
+ this.baseUrl = options.baseUrl;
34
+ this.model = options.model;
35
+ this.temperature = options.temperature;
36
+ this.maxTokens = options.maxTokens;
37
+ }
38
+ async generate(prompt, context) {
39
+ const messages = this.buildMessages(prompt, context);
40
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Authorization: `Bearer ${this.apiKey}`,
45
+ },
46
+ body: JSON.stringify({
47
+ model: this.model,
48
+ messages,
49
+ temperature: this.temperature,
50
+ max_tokens: this.maxTokens,
51
+ }),
52
+ });
53
+ if (!response.ok) {
54
+ const error = await response.text();
55
+ throw new Error(`OpenAI LLM failed: ${response.status} ${error}`);
56
+ }
57
+ const data = (await response.json());
58
+ return data.choices[0]?.message?.content ?? "";
59
+ }
60
+ async *generateStream(prompt, context) {
61
+ const messages = this.buildMessages(prompt, context);
62
+ const response = await fetch(`${this.baseUrl}/chat/completions`, {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ Authorization: `Bearer ${this.apiKey}`,
67
+ },
68
+ body: JSON.stringify({
69
+ model: this.model,
70
+ messages,
71
+ temperature: this.temperature,
72
+ max_tokens: this.maxTokens,
73
+ stream: true,
74
+ }),
75
+ });
76
+ if (!response.ok) {
77
+ const error = await response.text();
78
+ throw new Error(`OpenAI LLM stream failed: ${response.status} ${error}`);
79
+ }
80
+ if (!response.body) {
81
+ throw new Error("OpenAI LLM returned no body");
82
+ }
83
+ const reader = response.body.getReader();
84
+ const decoder = new TextDecoder();
85
+ let buffer = "";
86
+ try {
87
+ while (true) {
88
+ const { done, value } = await reader.read();
89
+ if (done)
90
+ break;
91
+ buffer += decoder.decode(value, { stream: true });
92
+ const lines = buffer.split("\n");
93
+ buffer = lines.pop() ?? "";
94
+ for (const line of lines) {
95
+ const trimmed = line.trim();
96
+ if (!trimmed || !trimmed.startsWith("data: "))
97
+ continue;
98
+ const jsonStr = trimmed.slice(6);
99
+ if (jsonStr === "[DONE]")
100
+ return;
101
+ try {
102
+ const parsed = JSON.parse(jsonStr);
103
+ const content = parsed.choices?.[0]?.delta?.content;
104
+ if (content) {
105
+ yield content;
106
+ }
107
+ }
108
+ catch {
109
+ // Ignore malformed JSON chunks
110
+ }
111
+ }
112
+ }
113
+ }
114
+ finally {
115
+ reader.releaseLock();
116
+ }
117
+ }
118
+ buildMessages(prompt, context) {
119
+ const messages = [];
120
+ // System prompt
121
+ if (context?.systemPrompt) {
122
+ messages.push({ role: "system", content: context.systemPrompt });
123
+ }
124
+ // Additional instructions
125
+ if (context?.instructions) {
126
+ messages.push({ role: "system", content: context.instructions });
127
+ }
128
+ // Conversation history
129
+ if (context?.history) {
130
+ messages.push(...context.history);
131
+ }
132
+ // The actual prompt (what JEV decided the agent should do)
133
+ messages.push({ role: "user", content: prompt });
134
+ return messages;
135
+ }
136
+ }
137
+ export function createOpenAILLM(options) {
138
+ return new OpenAILLM(options);
139
+ }
140
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/llm/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { ConversationTurn, MemoryManager, ConversationContext, Session } from "../types.js";
3
+ /**
4
+ * ConversationMemory — Manages the sliding window of conversation turns,
5
+ * extracted slots, and builds the full context for JEV.
6
+ *
7
+ * This is the agent's short-term memory for a single session.
8
+ */
9
+ export declare class ConversationMemory extends EventEmitter implements MemoryManager {
10
+ private turns;
11
+ private slots;
12
+ private readonly maxTurns;
13
+ constructor(options?: {
14
+ maxTurns?: number;
15
+ });
16
+ addTurn(turn: ConversationTurn): void;
17
+ getTurns(): ConversationTurn[];
18
+ getRecentTurns(n: number): ConversationTurn[];
19
+ setSlot(key: string, value: unknown): void;
20
+ getSlot(key: string): unknown;
21
+ getSlots(): Record<string, unknown>;
22
+ clear(): void;
23
+ /**
24
+ * Build the full conversation context for JEV.
25
+ * This is the primary input to the JEV engine's encode() method.
26
+ */
27
+ buildContext(session: Session, systemPrompt: string, currentUtterance?: string): ConversationContext;
28
+ /**
29
+ * Serialize the conversation to a format suitable for LLM context.
30
+ * Returns an array of {role, content} messages.
31
+ */
32
+ toLLMHistory(): Array<{
33
+ role: "user" | "assistant";
34
+ content: string;
35
+ }>;
36
+ /**
37
+ * Get a text summary of the conversation so far.
38
+ * Useful for embedding into the JEV context vector.
39
+ */
40
+ toContextString(): string;
41
+ /** Number of turns in memory */
42
+ get length(): number;
43
+ /** Get the last turn (or undefined if empty) */
44
+ get lastTurn(): ConversationTurn | undefined;
45
+ }
46
+ /**
47
+ * Create a new ConversationMemory instance.
48
+ * Factory function for convenience.
49
+ */
50
+ export declare function createMemory(options?: {
51
+ maxTurns?: number;
52
+ }): ConversationMemory;
53
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/memory/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,OAAO,EACR,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,qBAAa,kBAAmB,SAAQ,YAAa,YAAW,aAAa;IAC3E,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IAK3C,OAAO,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;IAYrC,QAAQ,IAAI,gBAAgB,EAAE;IAI9B,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAI7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAM1C,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI7B,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAInC,KAAK,IAAI,IAAI;IAMb;;;OAGG;IACH,YAAY,CACV,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,gBAAgB,CAAC,EAAE,MAAM,GACxB,mBAAmB;IAUtB;;;OAGG;IACH,YAAY,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAOtE;;;OAGG;IACH,eAAe,IAAI,MAAM;IAMzB,gCAAgC;IAChC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,gDAAgD;IAChD,IAAI,QAAQ,IAAI,gBAAgB,GAAG,SAAS,CAE3C;CACF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,kBAAkB,CAEpB"}
@@ -0,0 +1,95 @@
1
+ import { EventEmitter } from "node:events";
2
+ /**
3
+ * ConversationMemory — Manages the sliding window of conversation turns,
4
+ * extracted slots, and builds the full context for JEV.
5
+ *
6
+ * This is the agent's short-term memory for a single session.
7
+ */
8
+ export class ConversationMemory extends EventEmitter {
9
+ turns = [];
10
+ slots = new Map();
11
+ maxTurns;
12
+ constructor(options) {
13
+ super();
14
+ this.maxTurns = options?.maxTurns ?? 50;
15
+ }
16
+ addTurn(turn) {
17
+ this.turns.push(turn);
18
+ // Sliding window — drop oldest turns if we exceed the max
19
+ if (this.turns.length > this.maxTurns) {
20
+ const dropped = this.turns.shift();
21
+ this.emit("turnDropped", dropped);
22
+ }
23
+ this.emit("turnAdded", turn);
24
+ }
25
+ getTurns() {
26
+ return [...this.turns];
27
+ }
28
+ getRecentTurns(n) {
29
+ return this.turns.slice(-n);
30
+ }
31
+ setSlot(key, value) {
32
+ const previous = this.slots.get(key);
33
+ this.slots.set(key, value);
34
+ this.emit("slotUpdated", { key, value, previous });
35
+ }
36
+ getSlot(key) {
37
+ return this.slots.get(key);
38
+ }
39
+ getSlots() {
40
+ return Object.fromEntries(this.slots);
41
+ }
42
+ clear() {
43
+ this.turns = [];
44
+ this.slots.clear();
45
+ this.emit("cleared");
46
+ }
47
+ /**
48
+ * Build the full conversation context for JEV.
49
+ * This is the primary input to the JEV engine's encode() method.
50
+ */
51
+ buildContext(session, systemPrompt, currentUtterance) {
52
+ return {
53
+ session,
54
+ turns: this.getTurns(),
55
+ currentUtterance: currentUtterance ?? "",
56
+ slots: this.getSlots(),
57
+ systemPrompt,
58
+ };
59
+ }
60
+ /**
61
+ * Serialize the conversation to a format suitable for LLM context.
62
+ * Returns an array of {role, content} messages.
63
+ */
64
+ toLLMHistory() {
65
+ return this.turns.map((turn) => ({
66
+ role: turn.role === "user" ? "user" : "assistant",
67
+ content: turn.content,
68
+ }));
69
+ }
70
+ /**
71
+ * Get a text summary of the conversation so far.
72
+ * Useful for embedding into the JEV context vector.
73
+ */
74
+ toContextString() {
75
+ return this.turns
76
+ .map((t) => `${t.role === "user" ? "User" : "Agent"}: ${t.content}`)
77
+ .join("\n");
78
+ }
79
+ /** Number of turns in memory */
80
+ get length() {
81
+ return this.turns.length;
82
+ }
83
+ /** Get the last turn (or undefined if empty) */
84
+ get lastTurn() {
85
+ return this.turns[this.turns.length - 1];
86
+ }
87
+ }
88
+ /**
89
+ * Create a new ConversationMemory instance.
90
+ * Factory function for convenience.
91
+ */
92
+ export function createMemory(options) {
93
+ return new ConversationMemory(options);
94
+ }
95
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/memory/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { ConversationMemory, createMemory } from "./context.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { ConversationMemory, createMemory } from "./context.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { AudioChunk, Session, STTProvider, TTSProvider, VADProvider, LLMInterface, AgentHooks } from "./types.js";
3
+ import type { JEVEngine } from "./jev/engine.js";
4
+ import type { ConversationMemory } from "./memory/context.js";
5
+ import type { ToolRegistry } from "./tools/registry.js";
6
+ import type { CallLogger } from "./analytics/logger.js";
7
+ /**
8
+ * VoicePipeline — Orchestrates the full voice conversation loop.
9
+ *
10
+ * Audio In → VAD → STT → JEV Decision → LLM Generate → TTS → Audio Out
11
+ *
12
+ * One pipeline instance is created per active session (call).
13
+ * It manages:
14
+ * - Audio buffering and VAD-based turn detection
15
+ * - Streaming STT transcription
16
+ * - JEV-powered action selection
17
+ * - LLM response generation
18
+ * - Streaming TTS playback
19
+ * - Barge-in (user interruption) handling
20
+ */
21
+ export declare class VoicePipeline extends EventEmitter {
22
+ private readonly sessionId;
23
+ private readonly session;
24
+ private readonly stt;
25
+ private readonly tts;
26
+ private readonly vad;
27
+ private readonly llm;
28
+ private readonly jev;
29
+ private readonly memory;
30
+ private readonly tools;
31
+ private readonly logger;
32
+ private readonly hooks;
33
+ private readonly systemPrompt;
34
+ private sttStream;
35
+ private isSpeaking;
36
+ private isProcessing;
37
+ private currentTranscript;
38
+ private decisions;
39
+ private sendAudioFn;
40
+ constructor(options: {
41
+ sessionId: string;
42
+ session: Session;
43
+ stt: STTProvider;
44
+ tts: TTSProvider;
45
+ vad: VADProvider;
46
+ llm: LLMInterface;
47
+ jev: JEVEngine;
48
+ memory: ConversationMemory;
49
+ tools: ToolRegistry;
50
+ logger: CallLogger;
51
+ hooks: AgentHooks;
52
+ systemPrompt: string;
53
+ sendAudio: (chunk: AudioChunk) => Promise<void>;
54
+ });
55
+ /**
56
+ * Start the pipeline — initialize STT stream and begin processing.
57
+ */
58
+ start(): Promise<void>;
59
+ /**
60
+ * Process incoming audio from the transport.
61
+ * This is called for every audio chunk received from the client.
62
+ */
63
+ processAudio(chunk: AudioChunk): void;
64
+ /**
65
+ * Stop the pipeline — clean up resources.
66
+ */
67
+ stop(): Promise<void>;
68
+ /**
69
+ * Handle a STT transcription result.
70
+ */
71
+ private handleSTTResult;
72
+ /**
73
+ * Handle user turn completion — the user has stopped speaking.
74
+ * This triggers the JEV → LLM → TTS pipeline.
75
+ */
76
+ private handleUserTurnComplete;
77
+ /**
78
+ * Speak text through TTS and send audio back to the client.
79
+ */
80
+ private speak;
81
+ /**
82
+ * Handle barge-in — user interrupts while agent is speaking.
83
+ * Stop TTS playback immediately and re-enter listening mode.
84
+ */
85
+ private handleBargeIn;
86
+ /** Get current pipeline state */
87
+ get state(): {
88
+ isSpeaking: boolean;
89
+ isProcessing: boolean;
90
+ };
91
+ }
92
+ export declare function createPipeline(options: ConstructorParameters<typeof VoicePipeline>[0]): VoicePipeline;
93
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EACV,UAAU,EACV,OAAO,EACP,WAAW,EAEX,WAAW,EACX,WAAW,EACX,YAAY,EAGZ,UAAU,EACX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,uBAAuB,CAAC;AAExE;;;;;;;;;;;;;GAaG;AACH,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAc;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IAEtC,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,SAAS,CAAwB;IAGzC,OAAO,CAAC,WAAW,CAAuD;gBAE9D,OAAO,EAAE;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,EAAE,WAAW,CAAC;QACjB,GAAG,EAAE,WAAW,CAAC;QACjB,GAAG,EAAE,WAAW,CAAC;QACjB,GAAG,EAAE,YAAY,CAAC;QAClB,GAAG,EAAE,SAAS,CAAC;QACf,MAAM,EAAE,kBAAkB,CAAC;QAC3B,KAAK,EAAE,YAAY,CAAC;QACpB,MAAM,EAAE,UAAU,CAAC;QACnB,KAAK,EAAE,UAAU,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACjD;IAiBD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB5B;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAoBrC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA4B3B;;OAEG;IACH,OAAO,CAAC,eAAe;IAqBvB;;;OAGG;YACW,sBAAsB;IAqFpC;;OAEG;YACW,KAAK;IAwBnB;;;OAGG;IACH,OAAO,CAAC,aAAa;IASrB,iCAAiC;IACjC,IAAI,KAAK,IAAI;QAAE,UAAU,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAK1D;CACF;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,GACtD,aAAa,CAEf"}
@@ -0,0 +1,255 @@
1
+ import { EventEmitter } from "node:events";
2
+ /**
3
+ * VoicePipeline — Orchestrates the full voice conversation loop.
4
+ *
5
+ * Audio In → VAD → STT → JEV Decision → LLM Generate → TTS → Audio Out
6
+ *
7
+ * One pipeline instance is created per active session (call).
8
+ * It manages:
9
+ * - Audio buffering and VAD-based turn detection
10
+ * - Streaming STT transcription
11
+ * - JEV-powered action selection
12
+ * - LLM response generation
13
+ * - Streaming TTS playback
14
+ * - Barge-in (user interruption) handling
15
+ */
16
+ export class VoicePipeline extends EventEmitter {
17
+ sessionId;
18
+ session;
19
+ stt;
20
+ tts;
21
+ vad;
22
+ llm;
23
+ jev;
24
+ memory;
25
+ tools;
26
+ logger;
27
+ hooks;
28
+ systemPrompt;
29
+ sttStream = null;
30
+ isSpeaking = false; // Is the agent currently speaking?
31
+ isProcessing = false; // Is the pipeline processing a user turn?
32
+ currentTranscript = "";
33
+ decisions = [];
34
+ // Callback to send audio back to the client
35
+ sendAudioFn = null;
36
+ constructor(options) {
37
+ super();
38
+ this.sessionId = options.sessionId;
39
+ this.session = options.session;
40
+ this.stt = options.stt;
41
+ this.tts = options.tts;
42
+ this.vad = options.vad;
43
+ this.llm = options.llm;
44
+ this.jev = options.jev;
45
+ this.memory = options.memory;
46
+ this.tools = options.tools;
47
+ this.logger = options.logger;
48
+ this.hooks = options.hooks;
49
+ this.systemPrompt = options.systemPrompt;
50
+ this.sendAudioFn = options.sendAudio;
51
+ }
52
+ /**
53
+ * Start the pipeline — initialize STT stream and begin processing.
54
+ */
55
+ async start() {
56
+ this.logger.log("info", `Pipeline started for session ${this.sessionId}`);
57
+ // Create STT stream
58
+ this.sttStream = this.stt.createStream({
59
+ interimResults: true,
60
+ language: "en-US",
61
+ });
62
+ // Handle STT results
63
+ this.sttStream.onResult((result) => {
64
+ this.handleSTTResult(result);
65
+ });
66
+ // Notify hooks
67
+ await this.hooks.onCallStart?.(this.session);
68
+ this.emit("started");
69
+ }
70
+ /**
71
+ * Process incoming audio from the transport.
72
+ * This is called for every audio chunk received from the client.
73
+ */
74
+ processAudio(chunk) {
75
+ // Step 1: VAD — detect if the user is speaking
76
+ const vadResult = this.vad.process(chunk);
77
+ // Step 2: Handle barge-in — user starts speaking while agent is talking
78
+ if (vadResult.event?.type === "speech_start" && this.isSpeaking) {
79
+ this.handleBargeIn();
80
+ }
81
+ // Step 3: Feed audio to STT (only when speech is detected)
82
+ if (vadResult.isSpeech && this.sttStream) {
83
+ this.sttStream.write(chunk);
84
+ }
85
+ // Step 4: Handle speech end — user stopped speaking, process the turn
86
+ if (vadResult.event?.type === "speech_end" && !this.isProcessing) {
87
+ this.handleUserTurnComplete();
88
+ }
89
+ }
90
+ /**
91
+ * Stop the pipeline — clean up resources.
92
+ */
93
+ async stop() {
94
+ // Log the call
95
+ await this.logger.logCall({
96
+ session: this.session,
97
+ turns: this.memory.getTurns(),
98
+ decisions: this.decisions,
99
+ metrics: {
100
+ totalTurns: this.memory.length,
101
+ avgJEVLatencyMs: this.decisions.length > 0
102
+ ? this.decisions.reduce((sum, d) => sum + d.latencyMs, 0) / this.decisions.length
103
+ : 0,
104
+ bargeInCount: 0, // TODO: track this
105
+ avgConfidence: this.decisions.length > 0
106
+ ? this.decisions.reduce((sum, d) => sum + d.confidence, 0) / this.decisions.length
107
+ : 0,
108
+ },
109
+ });
110
+ // Close STT stream
111
+ await this.sttStream?.close();
112
+ // Notify hooks
113
+ await this.hooks.onCallEnd?.(this.session, this.memory.getTurns());
114
+ this.logger.log("info", `Pipeline stopped for session ${this.sessionId}`);
115
+ this.emit("stopped");
116
+ }
117
+ /**
118
+ * Handle a STT transcription result.
119
+ */
120
+ handleSTTResult(result) {
121
+ if (result.isFinal) {
122
+ // Accumulate final transcripts
123
+ this.currentTranscript += (this.currentTranscript ? " " : "") + result.text;
124
+ this.emit("transcript", {
125
+ text: this.currentTranscript,
126
+ isFinal: true,
127
+ });
128
+ }
129
+ else {
130
+ // Emit interim for real-time feedback
131
+ this.emit("transcript", {
132
+ text: result.text,
133
+ isFinal: false,
134
+ });
135
+ }
136
+ }
137
+ /**
138
+ * Handle user turn completion — the user has stopped speaking.
139
+ * This triggers the JEV → LLM → TTS pipeline.
140
+ */
141
+ async handleUserTurnComplete() {
142
+ const userText = this.currentTranscript.trim();
143
+ if (!userText)
144
+ return; // Ignore empty turns
145
+ this.isProcessing = true;
146
+ this.currentTranscript = "";
147
+ try {
148
+ // Add user turn to memory
149
+ this.memory.addTurn({
150
+ role: "user",
151
+ content: userText,
152
+ timestampMs: Date.now() - this.session.startedAt.getTime(),
153
+ });
154
+ // Notify hooks
155
+ await this.hooks.onUserSpoke?.(userText, this.session);
156
+ // Build conversation context for JEV
157
+ const context = this.memory.buildContext(this.session, this.systemPrompt, userText);
158
+ // JEV Decision — what should the agent do?
159
+ const startTime = performance.now();
160
+ const match = await this.jev.decide(context);
161
+ const jevLatencyMs = performance.now() - startTime;
162
+ // Log the decision
163
+ const decisionLog = {
164
+ timestampMs: Date.now() - this.session.startedAt.getTime(),
165
+ contextSummary: userText.slice(0, 200),
166
+ selectedAction: match.action.id,
167
+ confidence: match.confidence,
168
+ candidates: match.candidates.slice(0, 5),
169
+ latencyMs: jevLatencyMs,
170
+ };
171
+ this.decisions.push(decisionLog);
172
+ this.logger.logDecision(decisionLog);
173
+ // Notify hooks
174
+ await this.hooks.onActionSelected?.(match.action, match.confidence, context);
175
+ this.logger.log("info", `JEV selected: "${match.action.id}" (confidence: ${match.confidence.toFixed(3)}, latency: ${jevLatencyMs.toFixed(1)}ms)`);
176
+ // Execute the action handler — this calls the LLM to generate response text
177
+ const actionContext = {
178
+ conversation: context,
179
+ llm: this.llm,
180
+ tools: this.tools,
181
+ memory: this.memory,
182
+ session: this.session,
183
+ };
184
+ const responseText = await match.action.handler(actionContext);
185
+ // Add agent turn to memory
186
+ this.memory.addTurn({
187
+ role: "agent",
188
+ content: responseText,
189
+ timestampMs: Date.now() - this.session.startedAt.getTime(),
190
+ actionId: match.action.id,
191
+ confidence: match.confidence,
192
+ });
193
+ // Notify hooks
194
+ await this.hooks.onAgentSpoke?.(responseText, this.session);
195
+ // TTS — speak the response
196
+ await this.speak(responseText);
197
+ }
198
+ catch (error) {
199
+ const err = error instanceof Error ? error : new Error(String(error));
200
+ this.logger.log("error", `Pipeline error: ${err.message}`);
201
+ await this.hooks.onError?.(err, this.session);
202
+ this.emit("error", err);
203
+ }
204
+ finally {
205
+ this.isProcessing = false;
206
+ }
207
+ }
208
+ /**
209
+ * Speak text through TTS and send audio back to the client.
210
+ */
211
+ async speak(text) {
212
+ if (!this.sendAudioFn)
213
+ return;
214
+ this.isSpeaking = true;
215
+ this.emit("agentSpeaking", true);
216
+ try {
217
+ for await (const chunk of this.tts.synthesize(text)) {
218
+ // Check if we've been interrupted (barge-in)
219
+ if (!this.isSpeaking)
220
+ break;
221
+ await this.sendAudioFn(chunk);
222
+ }
223
+ }
224
+ catch (error) {
225
+ this.logger.log("error", `TTS error: ${error instanceof Error ? error.message : String(error)}`);
226
+ }
227
+ finally {
228
+ this.isSpeaking = false;
229
+ this.emit("agentSpeaking", false);
230
+ }
231
+ }
232
+ /**
233
+ * Handle barge-in — user interrupts while agent is speaking.
234
+ * Stop TTS playback immediately and re-enter listening mode.
235
+ */
236
+ handleBargeIn() {
237
+ if (!this.isSpeaking)
238
+ return;
239
+ this.logger.log("info", "Barge-in detected — stopping agent speech");
240
+ this.isSpeaking = false;
241
+ this.hooks.onBargeIn?.(this.session);
242
+ this.emit("bargeIn");
243
+ }
244
+ /** Get current pipeline state */
245
+ get state() {
246
+ return {
247
+ isSpeaking: this.isSpeaking,
248
+ isProcessing: this.isProcessing,
249
+ };
250
+ }
251
+ }
252
+ export function createPipeline(options) {
253
+ return new VoicePipeline(options);
254
+ }
255
+ //# sourceMappingURL=pipeline.js.map