realtime-voice-agents 2.2.0 → 2.4.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/README.md +35 -1
- package/dist/{InMemorySessionStore-B5_rq61L.d.cts → InMemorySessionStore-CRBmmCA6.d.cts} +10 -6
- package/dist/{InMemorySessionStore-B5_rq61L.d.mts → InMemorySessionStore-CRBmmCA6.d.mts} +10 -6
- package/dist/index.cjs +236 -9
- package/dist/index.d.cts +172 -2
- package/dist/index.d.mts +172 -2
- package/dist/index.mjs +232 -10
- package/dist/store.d.cts +2 -2
- package/dist/store.d.mts +2 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -189,6 +189,105 @@ function createHandoffTool(target) {
|
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
//#endregion
|
|
192
|
+
//#region src/dtmf/KeypadCollector.ts
|
|
193
|
+
const DEFAULT_KEYPAD_OPTIONS = {
|
|
194
|
+
submitKey: "#",
|
|
195
|
+
clearKey: "*",
|
|
196
|
+
interDigitTimeoutMs: 4e3,
|
|
197
|
+
interruptOnKeypress: true
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* Appended to the agent instructions when keypad input is enabled. Short and
|
|
201
|
+
* neutral on purpose: what the messages are, not how to run the dialog.
|
|
202
|
+
*/
|
|
203
|
+
const DEFAULT_KEYPAD_INSTRUCTIONS = "Keypad input: the caller may type on their phone keypad instead of speaking. Keypresses arrive as a user message starting with \"[keypad]\" that contains the typed digits — treat it as the caller's answer.";
|
|
204
|
+
/** Default text of the user turn injected for a completed entry. */
|
|
205
|
+
function defaultKeypadMessage(entry) {
|
|
206
|
+
const { digits } = entry;
|
|
207
|
+
return `[keypad] I typed on my phone keypad: ${digits} — ${digits.length} digit${digits.length === 1 ? "" : "s"}. Digit by digit: ${[...digits].join(" ")}`;
|
|
208
|
+
}
|
|
209
|
+
/** Default text of the user turn injected when the caller presses the clear key. */
|
|
210
|
+
const DEFAULT_KEYPAD_CLEAR_MESSAGE = "[keypad] I pressed star — I want to start over and retype from the beginning.";
|
|
211
|
+
var KeypadCollector = class {
|
|
212
|
+
submitKey;
|
|
213
|
+
clearKey;
|
|
214
|
+
interDigitTimeoutMs;
|
|
215
|
+
maxDigits;
|
|
216
|
+
hooks;
|
|
217
|
+
buffer = "";
|
|
218
|
+
timer = null;
|
|
219
|
+
disposed = false;
|
|
220
|
+
constructor(options, hooks) {
|
|
221
|
+
this.submitKey = options.submitKey ?? DEFAULT_KEYPAD_OPTIONS.submitKey;
|
|
222
|
+
this.clearKey = options.clearKey ?? DEFAULT_KEYPAD_OPTIONS.clearKey;
|
|
223
|
+
this.interDigitTimeoutMs = options.interDigitTimeoutMs ?? DEFAULT_KEYPAD_OPTIONS.interDigitTimeoutMs;
|
|
224
|
+
this.maxDigits = options.maxDigits !== void 0 && options.maxDigits > 0 ? options.maxDigits : void 0;
|
|
225
|
+
this.hooks = hooks;
|
|
226
|
+
}
|
|
227
|
+
get digits() {
|
|
228
|
+
return this.buffer;
|
|
229
|
+
}
|
|
230
|
+
/** Feed one keypress (a Twilio `dtmf` frame). Returns what the key meant. */
|
|
231
|
+
press(key) {
|
|
232
|
+
if (this.disposed) return "ignored";
|
|
233
|
+
if (key === this.submitKey) {
|
|
234
|
+
this.cancelTimer();
|
|
235
|
+
this.complete("submit");
|
|
236
|
+
return "submit";
|
|
237
|
+
}
|
|
238
|
+
if (key === this.clearKey) {
|
|
239
|
+
this.cancelTimer();
|
|
240
|
+
const discarded = this.buffer;
|
|
241
|
+
this.buffer = "";
|
|
242
|
+
this.hooks.onClear({ discarded });
|
|
243
|
+
return "clear";
|
|
244
|
+
}
|
|
245
|
+
if (!/^[0-9]$/.test(key)) return "ignored";
|
|
246
|
+
this.cancelTimer();
|
|
247
|
+
this.buffer += key;
|
|
248
|
+
if (this.maxDigits !== void 0 && this.buffer.length >= this.maxDigits) this.complete("maxDigits");
|
|
249
|
+
else this.armTimer();
|
|
250
|
+
return "digit";
|
|
251
|
+
}
|
|
252
|
+
clear() {
|
|
253
|
+
this.cancelTimer();
|
|
254
|
+
this.buffer = "";
|
|
255
|
+
}
|
|
256
|
+
submit() {
|
|
257
|
+
if (this.disposed) return;
|
|
258
|
+
this.cancelTimer();
|
|
259
|
+
this.complete("submit");
|
|
260
|
+
}
|
|
261
|
+
/** Release the timer; pending digits are dropped (the call is over). */
|
|
262
|
+
dispose() {
|
|
263
|
+
this.disposed = true;
|
|
264
|
+
this.cancelTimer();
|
|
265
|
+
this.buffer = "";
|
|
266
|
+
}
|
|
267
|
+
complete(reason) {
|
|
268
|
+
if (!this.buffer) return;
|
|
269
|
+
const digits = this.buffer;
|
|
270
|
+
this.buffer = "";
|
|
271
|
+
this.hooks.onEntry({
|
|
272
|
+
digits,
|
|
273
|
+
reason
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
armTimer() {
|
|
277
|
+
this.timer = setTimeout(() => {
|
|
278
|
+
this.timer = null;
|
|
279
|
+
this.complete("timeout");
|
|
280
|
+
}, this.interDigitTimeoutMs);
|
|
281
|
+
this.timer.unref?.();
|
|
282
|
+
}
|
|
283
|
+
cancelTimer() {
|
|
284
|
+
if (this.timer) {
|
|
285
|
+
clearTimeout(this.timer);
|
|
286
|
+
this.timer = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
//#endregion
|
|
192
291
|
//#region src/interruption/InterruptionController.ts
|
|
193
292
|
var InterruptionController = class {
|
|
194
293
|
settings;
|
|
@@ -697,9 +796,34 @@ function delayForAttempt(policy, attempt, random = Math.random) {
|
|
|
697
796
|
}
|
|
698
797
|
//#endregion
|
|
699
798
|
//#region src/session/transcript.ts
|
|
700
|
-
/**
|
|
701
|
-
|
|
702
|
-
|
|
799
|
+
/**
|
|
800
|
+
* Render a transcript for history re-injection after reconnect/handoff.
|
|
801
|
+
*
|
|
802
|
+
* Agent lines are attributed to the agent that said them and completed
|
|
803
|
+
* transfers are interleaved as `[transfer]` lines: an incoming agent handed a
|
|
804
|
+
* flat `Agent:` dialogue re-derives intent from scratch, decides the request
|
|
805
|
+
* belongs to somebody else, and transfers on — agents ping-ponging with no
|
|
806
|
+
* caller turn between them (field bug, Aug 2026). Replaying WHO said what and
|
|
807
|
+
* WHAT was already routed is what stops the second lap.
|
|
808
|
+
*/
|
|
809
|
+
function formatTranscriptForInjection(entries, options = {}) {
|
|
810
|
+
const { maxTurns = 30, agentNames, handoffs = [] } = options;
|
|
811
|
+
const recent = entries.slice(-maxTurns);
|
|
812
|
+
if (recent.length === 0) return "";
|
|
813
|
+
const nameOf = (agentId) => (agentId ? agentNames?.get(agentId) : void 0) ?? agentId ?? "Agent";
|
|
814
|
+
const windowStartMs = recent[0].timestampMs;
|
|
815
|
+
const lines = recent.map((entry) => ({
|
|
816
|
+
atMs: entry.timestampMs,
|
|
817
|
+
text: entry.role === "user" ? `Caller: ${entry.text}` : `${nameOf(entry.agentId)}: ${entry.text}`
|
|
818
|
+
}));
|
|
819
|
+
for (const handoff of handoffs) {
|
|
820
|
+
if (handoff.atMs < windowStartMs) continue;
|
|
821
|
+
lines.push({
|
|
822
|
+
atMs: handoff.atMs,
|
|
823
|
+
text: `[transfer] ${nameOf(handoff.from)} -> ${nameOf(handoff.to)}` + (handoff.reason ? ` (reason: ${handoff.reason})` : "")
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
return lines.sort((a, b) => a.atMs - b.atMs).map((line) => line.text).join("\n");
|
|
703
827
|
}
|
|
704
828
|
//#endregion
|
|
705
829
|
//#region src/session/usage.ts
|
|
@@ -889,11 +1013,17 @@ var CallSession = class extends TypedEmitter {
|
|
|
889
1013
|
streamSid;
|
|
890
1014
|
callInfo;
|
|
891
1015
|
context;
|
|
1016
|
+
/**
|
|
1017
|
+
* Keypad (DTMF) input handle: digits buffered so far, `clear()`, `submit()`.
|
|
1018
|
+
* Inert (empty, no-ops) unless the `keypad` session option is configured.
|
|
1019
|
+
*/
|
|
1020
|
+
keypad;
|
|
892
1021
|
stateValue = "connecting";
|
|
893
1022
|
deps;
|
|
894
1023
|
log;
|
|
895
1024
|
tracker = new PlaybackTracker();
|
|
896
1025
|
interruptions;
|
|
1026
|
+
keypadCollector;
|
|
897
1027
|
usageAccumulator = new UsageAccumulator();
|
|
898
1028
|
toolQueue = new ToolResultQueue();
|
|
899
1029
|
transcriptEntries = [];
|
|
@@ -940,6 +1070,15 @@ var CallSession = class extends TypedEmitter {
|
|
|
940
1070
|
agents;
|
|
941
1071
|
handoffHistory = [];
|
|
942
1072
|
handoffInProgress = false;
|
|
1073
|
+
/**
|
|
1074
|
+
* An agent that just took over cannot transfer again until the caller has
|
|
1075
|
+
* spoken. Without it every incoming agent re-derives intent from the same
|
|
1076
|
+
* replayed transcript, decides the request is not its own, and transfers on
|
|
1077
|
+
* — agents ping-ponging with no caller turn between them (field bug, Aug
|
|
1078
|
+
* 2026). Programmatic `handoffTo()` is host intent and bypasses the lock,
|
|
1079
|
+
* but still arms it for the agent it installs.
|
|
1080
|
+
*/
|
|
1081
|
+
handoffLockedUntilCallerTurn = false;
|
|
943
1082
|
/** Pre-synthesized greeting playout state. */
|
|
944
1083
|
pregreeting = null;
|
|
945
1084
|
/** Noise-adaptive VAD (opt-in); built after connect from the provider's ACKed config. */
|
|
@@ -967,6 +1106,18 @@ var CallSession = class extends TypedEmitter {
|
|
|
967
1106
|
this.activeAgentValue = deps.agent;
|
|
968
1107
|
this.context = new SessionContext(deps.options.context);
|
|
969
1108
|
this.interruptions = new InterruptionController(deps.options.interruptions);
|
|
1109
|
+
this.keypadCollector = deps.options.keypad ? new KeypadCollector(deps.options.keypad, {
|
|
1110
|
+
onEntry: (entry) => this.onKeypadEntry(entry),
|
|
1111
|
+
onClear: (info) => this.onKeypadCleared(info)
|
|
1112
|
+
}) : null;
|
|
1113
|
+
const collector = this.keypadCollector;
|
|
1114
|
+
this.keypad = {
|
|
1115
|
+
get digits() {
|
|
1116
|
+
return collector?.digits ?? "";
|
|
1117
|
+
},
|
|
1118
|
+
clear: () => collector?.clear(),
|
|
1119
|
+
submit: () => collector?.submit()
|
|
1120
|
+
};
|
|
970
1121
|
const params = deps.start.start.customParameters ?? {};
|
|
971
1122
|
this.callInfo = {
|
|
972
1123
|
direction: params.direction === "outbound" ? "outbound" : "inbound",
|
|
@@ -1208,9 +1359,16 @@ var CallSession = class extends TypedEmitter {
|
|
|
1208
1359
|
}
|
|
1209
1360
|
return tools;
|
|
1210
1361
|
}
|
|
1362
|
+
/** Agent instructions plus the kit's own notes that must survive handoffs (keypad). */
|
|
1363
|
+
composeInstructions() {
|
|
1364
|
+
const base = this.activeAgentValue.resolveInstructions(this.context);
|
|
1365
|
+
const keypad = this.deps.options.keypad;
|
|
1366
|
+
if (!keypad || keypad.instructions === false) return base;
|
|
1367
|
+
return `${base}\n\n${keypad.instructions ?? "Keypad input: the caller may type on their phone keypad instead of speaking. Keypresses arrive as a user message starting with \"[keypad]\" that contains the typed digits — treat it as the caller's answer."}`;
|
|
1368
|
+
}
|
|
1211
1369
|
buildProviderInit() {
|
|
1212
1370
|
return {
|
|
1213
|
-
instructions: this.
|
|
1371
|
+
instructions: this.composeInstructions() + (this.pregreeting ? `\n\nYou already opened the call by saying: "${this.pregreeting.text}". Do not greet again — continue the conversation from there.` : ""),
|
|
1214
1372
|
voice: this.activeAgentValue.voice,
|
|
1215
1373
|
vad: this.vadOverride !== void 0 ? this.vadOverride : this.deps.options.vad,
|
|
1216
1374
|
bridgeOwnsInterruptions: true,
|
|
@@ -1233,7 +1391,9 @@ var CallSession = class extends TypedEmitter {
|
|
|
1233
1391
|
transport.on("dtmf", (event) => {
|
|
1234
1392
|
this.clearIdleTimer();
|
|
1235
1393
|
this.nudgeCount = 0;
|
|
1236
|
-
|
|
1394
|
+
const digit = event.dtmf.digit;
|
|
1395
|
+
this.handleKeypress(digit);
|
|
1396
|
+
this.emit("dtmf", { digit });
|
|
1237
1397
|
});
|
|
1238
1398
|
transport.on("stop", () => void this.teardown("caller-hangup"));
|
|
1239
1399
|
transport.on("close", () => void this.teardown("caller-hangup"));
|
|
@@ -1302,11 +1462,13 @@ var CallSession = class extends TypedEmitter {
|
|
|
1302
1462
|
};
|
|
1303
1463
|
this.transcriptEntries.push(entry);
|
|
1304
1464
|
this.nudgeCount = 0;
|
|
1465
|
+
this.handoffLockedUntilCallerTurn = false;
|
|
1305
1466
|
this.emit("transcript.user", entry);
|
|
1306
1467
|
});
|
|
1307
1468
|
provider.on("userSpeechStarted", () => {
|
|
1308
1469
|
this.userSpeechActive = true;
|
|
1309
1470
|
this.userSpeechStartedAtMs = Date.now();
|
|
1471
|
+
this.handoffLockedUntilCallerTurn = false;
|
|
1310
1472
|
this.clearIdleTimer();
|
|
1311
1473
|
this.nudgeCount = 0;
|
|
1312
1474
|
this.emit("user.speech.started");
|
|
@@ -1602,6 +1764,17 @@ var CallSession = class extends TypedEmitter {
|
|
|
1602
1764
|
this.deliverToolResult(call.id, { error: `unknown agent "${directive.targetAgentId}"` });
|
|
1603
1765
|
return;
|
|
1604
1766
|
}
|
|
1767
|
+
if (this.handoffLockedUntilCallerTurn) {
|
|
1768
|
+
this.rejectHandoff(target, directive.reason, call.id);
|
|
1769
|
+
this.emit("tool.completed", {
|
|
1770
|
+
...baseInfo,
|
|
1771
|
+
strategy: tool.strategy,
|
|
1772
|
+
input,
|
|
1773
|
+
result: { handoffBlocked: target.id },
|
|
1774
|
+
durationMs: Date.now() - started
|
|
1775
|
+
});
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1605
1778
|
this.deliverToolResult(call.id, {
|
|
1606
1779
|
status: "transferring_conversation",
|
|
1607
1780
|
to: target.name
|
|
@@ -1985,29 +2158,53 @@ var CallSession = class extends TypedEmitter {
|
|
|
1985
2158
|
/** After a reconnect the provider session is blank — restore conversational context. */
|
|
1986
2159
|
reinjectHistory(provider) {
|
|
1987
2160
|
if (this.transcriptEntries.length === 0) return;
|
|
1988
|
-
const summary = formatTranscriptForInjection(this.transcriptEntries
|
|
1989
|
-
|
|
2161
|
+
const summary = formatTranscriptForInjection(this.transcriptEntries, {
|
|
2162
|
+
agentNames: new Map([...this.agents].map(([id, agent]) => [id, agent.name])),
|
|
2163
|
+
handoffs: this.handoffHistory
|
|
2164
|
+
});
|
|
2165
|
+
provider.sendText(`Context: this phone call reconnected mid-conversation. You are ${this.activeAgentValue.name}. Each line below names who said it; \`[transfer]\` lines are routing that ALREADY happened.\n${summary}\nAnything already answered or already routed above must not be routed again. Continue naturally from where it left off; do not greet again.`, {
|
|
1990
2166
|
role: "system",
|
|
1991
2167
|
triggerResponse: false
|
|
1992
2168
|
});
|
|
1993
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Refuse a model-initiated transfer from an agent the caller has not spoken
|
|
2172
|
+
* to yet. The refusal must reach the model as the tool result, or it simply
|
|
2173
|
+
* calls the same transfer tool again on its next turn.
|
|
2174
|
+
*/
|
|
2175
|
+
rejectHandoff(target, reason, callId) {
|
|
2176
|
+
const from = this.activeAgentValue;
|
|
2177
|
+
this.log.warn(`blocked transfer ${from.id} -> ${target.id}: the caller has not spoken since the last one`);
|
|
2178
|
+
this.deliverToolResult(callId, {
|
|
2179
|
+
error: "transfer_rejected",
|
|
2180
|
+
message: "You just took over this call and the caller has not spoken since. Do not transfer again yet — handle their request yourself, or ask them what they need. You may transfer once they reply."
|
|
2181
|
+
});
|
|
2182
|
+
this.emit("agent.handoff.blocked", {
|
|
2183
|
+
from,
|
|
2184
|
+
to: target,
|
|
2185
|
+
reason,
|
|
2186
|
+
cause: "no-caller-turn"
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
1994
2189
|
async performHandoff(target, reason) {
|
|
1995
2190
|
if (this.stateValue !== "active" || !this.provider) return;
|
|
1996
2191
|
if (target.id === this.activeAgentValue.id) return;
|
|
1997
2192
|
const from = this.activeAgentValue;
|
|
1998
2193
|
this.activeAgentValue = target;
|
|
2194
|
+
this.handoffLockedUntilCallerTurn = true;
|
|
1999
2195
|
this.toolset = this.buildToolset();
|
|
2000
2196
|
this.handoffHistory.push({
|
|
2001
2197
|
from: from.id,
|
|
2002
2198
|
to: target.id,
|
|
2003
|
-
atMs: Date.now() - this.startedAtMs
|
|
2199
|
+
atMs: Date.now() - this.startedAtMs,
|
|
2200
|
+
...reason ? { reason } : {}
|
|
2004
2201
|
});
|
|
2005
2202
|
const continueInstruction = `You are now ${target.name}. Continue the SAME phone conversation naturally — acknowledge the caller and take over; do not restart with a cold greeting.` + (reason ? ` Transfer context: ${reason}.` : "");
|
|
2006
2203
|
const voiceChanges = target.voice !== void 0 && target.voice !== from.voice;
|
|
2007
2204
|
if (!(!this.provider.capabilities.sessionUpdate || voiceChanges && !this.provider.capabilities.voiceChangeMidSession && this.deps.options.handoffVoicePolicy === "reconnect")) {
|
|
2008
2205
|
if (voiceChanges && !this.provider.capabilities.voiceChangeMidSession) this.log.warn(`agent "${target.id}" declares voice "${target.voice}" but the provider cannot change voice mid-session — keeping the current voice (set handoffVoicePolicy: 'reconnect' to switch)`);
|
|
2009
2206
|
await this.provider.updateSession({
|
|
2010
|
-
instructions: this.
|
|
2207
|
+
instructions: this.composeInstructions(),
|
|
2011
2208
|
tools: this.buildProviderInit().tools,
|
|
2012
2209
|
providerOptions: target.providerOptions
|
|
2013
2210
|
});
|
|
@@ -2083,6 +2280,30 @@ var CallSession = class extends TypedEmitter {
|
|
|
2083
2280
|
this.transcriptEntries.push(entry);
|
|
2084
2281
|
this.emit("transcript.agent", entry);
|
|
2085
2282
|
}
|
|
2283
|
+
handleKeypress(key) {
|
|
2284
|
+
if (!this.keypadCollector) return;
|
|
2285
|
+
if (this.deps.options.keypad?.interruptOnKeypress !== false) this.interrupt();
|
|
2286
|
+
this.keypadCollector.press(key);
|
|
2287
|
+
}
|
|
2288
|
+
onKeypadEntry(entry) {
|
|
2289
|
+
this.handoffLockedUntilCallerTurn = false;
|
|
2290
|
+
this.emit("keypad.entry", entry);
|
|
2291
|
+
const message = this.deps.options.keypad?.message;
|
|
2292
|
+
if (message === false) return;
|
|
2293
|
+
this.provider?.sendText((message ?? defaultKeypadMessage)(entry), {
|
|
2294
|
+
role: "user",
|
|
2295
|
+
triggerResponse: true
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
onKeypadCleared(info) {
|
|
2299
|
+
this.emit("keypad.cleared", info);
|
|
2300
|
+
const clearMessage = this.deps.options.keypad?.clearMessage;
|
|
2301
|
+
if (clearMessage === false) return;
|
|
2302
|
+
this.provider?.sendText(clearMessage ?? "[keypad] I pressed star — I want to start over and retype from the beginning.", {
|
|
2303
|
+
role: "user",
|
|
2304
|
+
triggerResponse: true
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2086
2307
|
/** Armed whenever the agent goes quiet and we're waiting on the caller. */
|
|
2087
2308
|
armIdleTimer() {
|
|
2088
2309
|
const idle = this.deps.options.idle;
|
|
@@ -2142,6 +2363,7 @@ var CallSession = class extends TypedEmitter {
|
|
|
2142
2363
|
for (const timer of this.timers) clearTimeout(timer);
|
|
2143
2364
|
this.timers.clear();
|
|
2144
2365
|
this.clearIdleTimer();
|
|
2366
|
+
this.keypadCollector?.dispose();
|
|
2145
2367
|
this.bgAudio.stop({ immediate: true });
|
|
2146
2368
|
for (const controller of this.runningTools.values()) controller.abort(/* @__PURE__ */ new Error("call ended"));
|
|
2147
2369
|
this.runningTools.clear();
|
|
@@ -2449,4 +2671,4 @@ async function captureGreetingAudio(options) {
|
|
|
2449
2671
|
});
|
|
2450
2672
|
}
|
|
2451
2673
|
//#endregion
|
|
2452
|
-
export { Agent, BaseRealtimeProvider, CallSession, DEFAULT_RECONNECT_POLICY, DEFAULT_SESSION_OPTIONS, InMemorySessionStore, InterruptionController, NoiseAdaptiveVadController, PlaybackTracker, SessionContext, TwilioRealtimeBridge, captureGreetingAudio, collectAgentGraph, composeExecution, connectStreamTwiml, consoleLogger, createFinishCallTool, createHandoffTool, createTransferCallTool, decorateTool, emptyUsage, handoffToolName, isHandoffDirective, noopLogger, resolveSessionOptions, tool, zodToJsonSchema };
|
|
2674
|
+
export { Agent, BaseRealtimeProvider, CallSession, DEFAULT_KEYPAD_CLEAR_MESSAGE, DEFAULT_KEYPAD_INSTRUCTIONS, DEFAULT_KEYPAD_OPTIONS, DEFAULT_RECONNECT_POLICY, DEFAULT_SESSION_OPTIONS, InMemorySessionStore, InterruptionController, KeypadCollector, NoiseAdaptiveVadController, PlaybackTracker, SessionContext, TwilioRealtimeBridge, captureGreetingAudio, collectAgentGraph, composeExecution, connectStreamTwiml, consoleLogger, createFinishCallTool, createHandoffTool, createTransferCallTool, decorateTool, defaultKeypadMessage, emptyUsage, handoffToolName, isHandoffDirective, noopLogger, resolveSessionOptions, tool, zodToJsonSchema };
|
package/dist/store.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as UsageInfo, n as SessionStore, o as
|
|
2
|
-
export { type CallSnapshot, InMemorySessionStore, type SessionStore, type TranscriptEntry, type UsageInfo };
|
|
1
|
+
import { i as UsageInfo, n as SessionStore, o as HandoffRecord, r as CallSnapshot, s as TranscriptEntry, t as InMemorySessionStore } from "./InMemorySessionStore-CRBmmCA6.cjs";
|
|
2
|
+
export { type CallSnapshot, type HandoffRecord, InMemorySessionStore, type SessionStore, type TranscriptEntry, type UsageInfo };
|
package/dist/store.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as UsageInfo, n as SessionStore, o as
|
|
2
|
-
export { type CallSnapshot, InMemorySessionStore, type SessionStore, type TranscriptEntry, type UsageInfo };
|
|
1
|
+
import { i as UsageInfo, n as SessionStore, o as HandoffRecord, r as CallSnapshot, s as TranscriptEntry, t as InMemorySessionStore } from "./InMemorySessionStore-CRBmmCA6.mjs";
|
|
2
|
+
export { type CallSnapshot, type HandoffRecord, InMemorySessionStore, type SessionStore, type TranscriptEntry, type UsageInfo };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "realtime-voice-agents",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Provider-agnostic bridge between Twilio Media Streams and realtime speech-to-speech AI APIs (OpenAI Realtime, xAI Grok Voice, Gemini Live). Multi-agent handoffs, Zod tools with execution strategies, mark-based playback tracking, interruption guards, and hold audio — for Node.js voice agents over the phone.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twilio",
|