@kognitivedev/backend-cloud 0.2.29

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 (43) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-test.log +14 -0
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +88 -0
  5. package/dist/cloud-voice-parameters.d.ts +11 -0
  6. package/dist/cloud-voice-parameters.js +219 -0
  7. package/dist/cloud-voice-prompt-service.d.ts +24 -0
  8. package/dist/cloud-voice-prompt-service.js +382 -0
  9. package/dist/cloud-voice-runtime-service.d.ts +73 -0
  10. package/dist/cloud-voice-runtime-service.js +443 -0
  11. package/dist/cloud-voice.d.ts +36 -0
  12. package/dist/cloud-voice.js +683 -0
  13. package/dist/index.d.ts +10 -0
  14. package/dist/index.js +26 -0
  15. package/dist/phone-control.d.ts +50 -0
  16. package/dist/phone-control.js +97 -0
  17. package/dist/phone-runtime/audio-playout-tracker.d.ts +51 -0
  18. package/dist/phone-runtime/audio-playout-tracker.js +93 -0
  19. package/dist/phone-runtime/openai-twilio-realtime.d.ts +95 -0
  20. package/dist/phone-runtime/openai-twilio-realtime.js +1074 -0
  21. package/dist/tools.d.ts +2 -0
  22. package/dist/tools.js +216 -0
  23. package/dist/types.d.ts +468 -0
  24. package/dist/types.js +2 -0
  25. package/dist/utils.d.ts +3 -0
  26. package/dist/utils.js +14 -0
  27. package/package.json +47 -0
  28. package/src/__tests__/audio-playout-tracker.test.ts +46 -0
  29. package/src/__tests__/cloud-voice.test.ts +1006 -0
  30. package/src/__tests__/openai-twilio-realtime.test.ts +1193 -0
  31. package/src/__tests__/phone-control.test.ts +105 -0
  32. package/src/cloud-voice-parameters.ts +236 -0
  33. package/src/cloud-voice-prompt-service.ts +493 -0
  34. package/src/cloud-voice-runtime-service.ts +465 -0
  35. package/src/cloud-voice.ts +831 -0
  36. package/src/index.ts +10 -0
  37. package/src/phone-control.ts +156 -0
  38. package/src/phone-runtime/audio-playout-tracker.ts +132 -0
  39. package/src/phone-runtime/openai-twilio-realtime.ts +1250 -0
  40. package/src/tools.ts +227 -0
  41. package/src/types.ts +529 -0
  42. package/src/utils.ts +11 -0
  43. package/tsconfig.json +13 -0
@@ -0,0 +1,10 @@
1
+ export * from "./cloud-voice";
2
+ export * from "./cloud-voice-parameters";
3
+ export * from "./cloud-voice-prompt-service";
4
+ export * from "./cloud-voice-runtime-service";
5
+ export * from "./phone-control";
6
+ export * from "./phone-runtime/audio-playout-tracker";
7
+ export * from "./phone-runtime/openai-twilio-realtime";
8
+ export * from "./tools";
9
+ export * from "./types";
10
+ export * from "./utils";
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./cloud-voice"), exports);
18
+ __exportStar(require("./cloud-voice-parameters"), exports);
19
+ __exportStar(require("./cloud-voice-prompt-service"), exports);
20
+ __exportStar(require("./cloud-voice-runtime-service"), exports);
21
+ __exportStar(require("./phone-control"), exports);
22
+ __exportStar(require("./phone-runtime/audio-playout-tracker"), exports);
23
+ __exportStar(require("./phone-runtime/openai-twilio-realtime"), exports);
24
+ __exportStar(require("./tools"), exports);
25
+ __exportStar(require("./types"), exports);
26
+ __exportStar(require("./utils"), exports);
@@ -0,0 +1,50 @@
1
+ import type { CloudVoiceSipControlAdapter, CloudVoicePhoneControlAdapter } from "./types";
2
+ export interface ExecuteCloudVoicePhoneControlToolInput {
3
+ toolId: string;
4
+ args: unknown;
5
+ provider: string;
6
+ providerCallId: string;
7
+ adapters: CloudVoicePhoneControlAdapter[] | Record<string, CloudVoicePhoneControlAdapter>;
8
+ metadata?: Record<string, unknown>;
9
+ }
10
+ export interface ExecuteCloudVoicePhoneControlToolResult {
11
+ result: {
12
+ ok: true;
13
+ provider: string;
14
+ providerCallId: string;
15
+ status: string;
16
+ reason: string;
17
+ };
18
+ raw?: unknown;
19
+ }
20
+ export interface ExecuteCloudVoiceSipTransferToolInput {
21
+ toolId: string;
22
+ args: unknown;
23
+ provider: string;
24
+ providerCallId: string;
25
+ adapters: CloudVoiceSipControlAdapter[] | Record<string, CloudVoiceSipControlAdapter>;
26
+ resolveDestination?: (destinationId: string) => {
27
+ destination: Parameters<CloudVoiceSipControlAdapter["transferCall"]>[0]["destination"];
28
+ policy?: Parameters<CloudVoiceSipControlAdapter["transferCall"]>[0]["policy"];
29
+ } | null | Promise<{
30
+ destination: Parameters<CloudVoiceSipControlAdapter["transferCall"]>[0]["destination"];
31
+ policy?: Parameters<CloudVoiceSipControlAdapter["transferCall"]>[0]["policy"];
32
+ } | null>;
33
+ metadata?: Record<string, unknown>;
34
+ }
35
+ export interface ExecuteCloudVoiceSipTransferToolResult {
36
+ result: {
37
+ ok: true;
38
+ provider: string;
39
+ providerCallId: string;
40
+ status: string;
41
+ transferId?: string;
42
+ mode: string;
43
+ destinationId: string;
44
+ reason: string;
45
+ };
46
+ raw?: unknown;
47
+ }
48
+ export declare function getPhoneControlToolReason(args: unknown): string;
49
+ export declare function executeCloudVoicePhoneControlTool(input: ExecuteCloudVoicePhoneControlToolInput): Promise<ExecuteCloudVoicePhoneControlToolResult>;
50
+ export declare function executeCloudVoiceSipTransferTool(input: ExecuteCloudVoiceSipTransferToolInput): Promise<ExecuteCloudVoiceSipTransferToolResult>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPhoneControlToolReason = getPhoneControlToolReason;
4
+ exports.executeCloudVoicePhoneControlTool = executeCloudVoicePhoneControlTool;
5
+ exports.executeCloudVoiceSipTransferTool = executeCloudVoiceSipTransferTool;
6
+ const cloud_voice_1 = require("./cloud-voice");
7
+ const utils_1 = require("./utils");
8
+ function getPhoneControlToolReason(args) {
9
+ return (0, utils_1.getString)((0, utils_1.getRecord)(args).reason, "agent_requested");
10
+ }
11
+ function resolvePhoneControlAdapter(adapters, provider) {
12
+ var _a, _b;
13
+ if (Array.isArray(adapters)) {
14
+ return (_a = adapters.find((adapter) => adapter.provider === provider)) !== null && _a !== void 0 ? _a : null;
15
+ }
16
+ return (_b = adapters[provider]) !== null && _b !== void 0 ? _b : null;
17
+ }
18
+ function resolveSipControlAdapter(adapters, provider) {
19
+ var _a, _b;
20
+ if (Array.isArray(adapters)) {
21
+ return (_a = adapters.find((adapter) => adapter.provider === provider)) !== null && _a !== void 0 ? _a : null;
22
+ }
23
+ return (_b = adapters[provider]) !== null && _b !== void 0 ? _b : null;
24
+ }
25
+ async function executeCloudVoicePhoneControlTool(input) {
26
+ if (input.toolId !== cloud_voice_1.CLOUD_VOICE_PHONE_HANGUP_TOOL.name) {
27
+ throw new Error(`Unknown Cloud Voice phone control tool "${input.toolId}"`);
28
+ }
29
+ const providerCallId = (0, utils_1.getString)(input.providerCallId, "");
30
+ if (!providerCallId)
31
+ throw new Error("Cloud Voice phone control tool is missing providerCallId");
32
+ const adapter = resolvePhoneControlAdapter(input.adapters, input.provider);
33
+ if (!adapter) {
34
+ throw new Error(`Phone control tool "${input.toolId}" is not supported for provider "${input.provider}"`);
35
+ }
36
+ const reason = getPhoneControlToolReason(input.args);
37
+ const result = await adapter.hangUpCall({
38
+ providerCallId,
39
+ reason,
40
+ metadata: input.metadata,
41
+ });
42
+ return {
43
+ result: {
44
+ ok: true,
45
+ provider: result.provider,
46
+ providerCallId: result.providerCallId,
47
+ status: result.status,
48
+ reason,
49
+ },
50
+ raw: result.raw,
51
+ };
52
+ }
53
+ async function executeCloudVoiceSipTransferTool(input) {
54
+ var _a, _b;
55
+ if (input.toolId !== cloud_voice_1.CLOUD_VOICE_SIP_TRANSFER_TOOL.name) {
56
+ throw new Error(`Unknown Cloud Voice SIP control tool "${input.toolId}"`);
57
+ }
58
+ const providerCallId = (0, utils_1.getString)(input.providerCallId, (0, utils_1.getString)((0, utils_1.getRecord)(input.args).providerCallId, ""));
59
+ if (!providerCallId)
60
+ throw new Error("Cloud Voice SIP transfer tool is missing providerCallId");
61
+ const adapter = resolveSipControlAdapter(input.adapters, input.provider);
62
+ if (!adapter) {
63
+ throw new Error(`SIP control tool "${input.toolId}" is not supported for provider "${input.provider}"`);
64
+ }
65
+ const args = (0, utils_1.getRecord)(input.args);
66
+ const destinationId = (0, utils_1.getString)(args.destinationId, "");
67
+ if (!destinationId)
68
+ throw new Error("Cloud Voice SIP transfer tool is missing destinationId");
69
+ const resolved = await ((_a = input.resolveDestination) === null || _a === void 0 ? void 0 : _a.call(input, destinationId));
70
+ if (!resolved)
71
+ throw new Error(`Cloud Voice SIP transfer destination "${destinationId}" was not found`);
72
+ const mode = args.mode === "attended" || args.mode === "warm" || args.mode === "blind"
73
+ ? args.mode
74
+ : (_b = resolved.policy) === null || _b === void 0 ? void 0 : _b.mode;
75
+ const reason = (0, utils_1.getString)(args.reason, "agent_requested_transfer");
76
+ const result = await adapter.transferCall({
77
+ providerCallId,
78
+ destination: resolved.destination,
79
+ mode,
80
+ reason,
81
+ policy: resolved.policy,
82
+ metadata: input.metadata,
83
+ });
84
+ return {
85
+ result: {
86
+ ok: true,
87
+ provider: result.provider,
88
+ providerCallId: result.providerCallId,
89
+ status: result.status,
90
+ transferId: result.transferId,
91
+ mode: result.mode,
92
+ destinationId,
93
+ reason,
94
+ },
95
+ raw: result.raw,
96
+ };
97
+ }
@@ -0,0 +1,51 @@
1
+ export interface PhonePlayoutInterruption {
2
+ assistantItemId: string | null;
3
+ heardAudioMs: number;
4
+ queuedAudioMs: number;
5
+ pendingMarks: number;
6
+ interrupted: true;
7
+ clearedAt: string;
8
+ providerTruncationSupported: boolean;
9
+ }
10
+ export interface PhonePlayoutTrackerOptions {
11
+ frameDurationMs?: number;
12
+ }
13
+ export interface PhonePlayoutSnapshot {
14
+ assistantItemId: string | null;
15
+ queuedAudioMs: number;
16
+ heardAudioMs: number;
17
+ pendingMarks: number;
18
+ lastActivityAt: number;
19
+ }
20
+ export interface PhonePlayoutIdleResult extends PhonePlayoutSnapshot {
21
+ idle: boolean;
22
+ reason: "idle" | "timeout";
23
+ waitedMs: number;
24
+ }
25
+ export declare class PhonePlayoutTracker {
26
+ private readonly frameDurationMs;
27
+ private assistantItemId;
28
+ private queuedAudioMs;
29
+ private heardAudioMs;
30
+ private pendingMarks;
31
+ private lastActivityAt;
32
+ constructor(options?: PhonePlayoutTrackerOptions);
33
+ startAssistantItem(itemId: string | null): void;
34
+ recordOutboundFrame(input?: {
35
+ markName?: string | null;
36
+ durationMs?: number;
37
+ itemId?: string | null;
38
+ }): void;
39
+ recordCarrierMark(markName?: string | null): void;
40
+ interrupt(input: {
41
+ elapsedMs?: number;
42
+ providerTruncationSupported: boolean;
43
+ }): PhonePlayoutInterruption;
44
+ reset(): void;
45
+ snapshot(): PhonePlayoutSnapshot;
46
+ waitForIdle(input?: {
47
+ timeoutMs?: number;
48
+ quietMs?: number;
49
+ pollMs?: number;
50
+ }): Promise<PhonePlayoutIdleResult>;
51
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PhonePlayoutTracker = void 0;
4
+ class PhonePlayoutTracker {
5
+ constructor(options = {}) {
6
+ var _a;
7
+ this.assistantItemId = null;
8
+ this.queuedAudioMs = 0;
9
+ this.heardAudioMs = 0;
10
+ this.pendingMarks = [];
11
+ this.lastActivityAt = Date.now();
12
+ this.frameDurationMs = (_a = options.frameDurationMs) !== null && _a !== void 0 ? _a : 20;
13
+ }
14
+ startAssistantItem(itemId) {
15
+ this.assistantItemId = itemId;
16
+ this.queuedAudioMs = 0;
17
+ this.heardAudioMs = 0;
18
+ this.pendingMarks = [];
19
+ this.lastActivityAt = Date.now();
20
+ }
21
+ recordOutboundFrame(input = {}) {
22
+ var _a;
23
+ if (input.itemId !== undefined && input.itemId !== this.assistantItemId) {
24
+ this.startAssistantItem(input.itemId);
25
+ }
26
+ this.queuedAudioMs += (_a = input.durationMs) !== null && _a !== void 0 ? _a : this.frameDurationMs;
27
+ if (input.markName)
28
+ this.pendingMarks.push(input.markName);
29
+ this.lastActivityAt = Date.now();
30
+ }
31
+ recordCarrierMark(markName) {
32
+ if (this.pendingMarks.length === 0)
33
+ return;
34
+ if (!markName) {
35
+ this.pendingMarks.shift();
36
+ this.heardAudioMs = Math.min(this.queuedAudioMs, this.heardAudioMs + this.frameDurationMs);
37
+ this.lastActivityAt = Date.now();
38
+ return;
39
+ }
40
+ const index = this.pendingMarks.indexOf(markName);
41
+ const consumed = index >= 0 ? index + 1 : 1;
42
+ this.pendingMarks.splice(0, consumed);
43
+ this.heardAudioMs = Math.min(this.queuedAudioMs, this.heardAudioMs + consumed * this.frameDurationMs);
44
+ this.lastActivityAt = Date.now();
45
+ }
46
+ interrupt(input) {
47
+ var _a;
48
+ const heardAudioMs = Math.max(this.heardAudioMs, Math.min((_a = input.elapsedMs) !== null && _a !== void 0 ? _a : this.queuedAudioMs, this.queuedAudioMs));
49
+ const event = {
50
+ assistantItemId: this.assistantItemId,
51
+ heardAudioMs,
52
+ queuedAudioMs: this.queuedAudioMs,
53
+ pendingMarks: this.pendingMarks.length,
54
+ interrupted: true,
55
+ clearedAt: new Date().toISOString(),
56
+ providerTruncationSupported: input.providerTruncationSupported,
57
+ };
58
+ this.reset();
59
+ return event;
60
+ }
61
+ reset() {
62
+ this.assistantItemId = null;
63
+ this.queuedAudioMs = 0;
64
+ this.heardAudioMs = 0;
65
+ this.pendingMarks = [];
66
+ this.lastActivityAt = Date.now();
67
+ }
68
+ snapshot() {
69
+ return {
70
+ assistantItemId: this.assistantItemId,
71
+ queuedAudioMs: this.queuedAudioMs,
72
+ heardAudioMs: this.heardAudioMs,
73
+ pendingMarks: this.pendingMarks.length,
74
+ lastActivityAt: this.lastActivityAt,
75
+ };
76
+ }
77
+ async waitForIdle(input = {}) {
78
+ var _a, _b, _c;
79
+ const startedAt = Date.now();
80
+ const timeoutMs = Math.max(0, (_a = input.timeoutMs) !== null && _a !== void 0 ? _a : 8000);
81
+ const quietMs = Math.max(0, (_b = input.quietMs) !== null && _b !== void 0 ? _b : 250);
82
+ const pollMs = Math.max(10, (_c = input.pollMs) !== null && _c !== void 0 ? _c : 50);
83
+ while (Date.now() - startedAt < timeoutMs) {
84
+ const snapshot = this.snapshot();
85
+ if (snapshot.pendingMarks === 0 && Date.now() - snapshot.lastActivityAt >= quietMs) {
86
+ return Object.assign(Object.assign({}, snapshot), { idle: true, reason: "idle", waitedMs: Date.now() - startedAt });
87
+ }
88
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
89
+ }
90
+ return Object.assign(Object.assign({}, this.snapshot()), { idle: false, reason: "timeout", waitedMs: Date.now() - startedAt });
91
+ }
92
+ }
93
+ exports.PhonePlayoutTracker = PhonePlayoutTracker;
@@ -0,0 +1,95 @@
1
+ import { type WebSocket } from "ws";
2
+ import type { CloudVoiceFunctionToolManifest, PreparedCloudVoiceConfig } from "../types";
3
+ import { type PhoneAudioRoute, type PhoneMediaProfile } from "@kognitivedev/voice-media-bridge";
4
+ export type OpenAITwilioRealtimeSnapshot = {
5
+ runtime?: Partial<PreparedCloudVoiceConfig["runtime"]>;
6
+ voiceConfig?: Partial<PreparedCloudVoiceConfig["voiceConfig"]>;
7
+ toolManifest?: Array<Partial<CloudVoiceFunctionToolManifest>>;
8
+ config?: Record<string, unknown>;
9
+ };
10
+ export type OpenAITwilioRealtimeCounters = {
11
+ inboundMediaFrames: number;
12
+ providerInputFrames: number;
13
+ providerInputDroppedBeforeReady: number;
14
+ outboundMediaFrames: number;
15
+ outboundAudioDroppedMissingStreamSid: number;
16
+ outboundAudioBytes: number;
17
+ outboundAudioPeakAbs: number;
18
+ outboundAudioLastRms: number;
19
+ clears: number;
20
+ truncates: number;
21
+ marks: number;
22
+ dtmf: number;
23
+ interruptions: number;
24
+ ignoredInterruptions: number;
25
+ toolCalls: number;
26
+ providerMessages: number;
27
+ };
28
+ type BridgeLog = (event: string, data?: Record<string, unknown>) => void;
29
+ type BridgeError = (event: string, error: unknown, data?: Record<string, unknown>) => void;
30
+ export type OpenAITwilioRealtimeBridgeInput = {
31
+ socket: WebSocket;
32
+ apiKey: string;
33
+ provider?: "openai-realtime" | "xai-realtime";
34
+ realtimeUrl?: string;
35
+ defaultModel?: string;
36
+ defaultVoice?: string;
37
+ mediaProfile?: PhoneMediaProfile;
38
+ audioRoute?: PhoneAudioRoute;
39
+ bridgeMode?: string;
40
+ supportsConversationItemTruncate?: boolean;
41
+ projectId: string;
42
+ sessionId: string;
43
+ providerCallId?: string | null;
44
+ snapshot: OpenAITwilioRealtimeSnapshot;
45
+ initialPrompt?: string | null;
46
+ log: BridgeLog;
47
+ error: BridgeError;
48
+ appendEvent: (eventType: string, payload?: Record<string, unknown>, message?: string) => void;
49
+ updateCallLegActive: (metadata: {
50
+ streamSid: string | null;
51
+ }) => void;
52
+ updateSessionActive: () => void;
53
+ onAudioFrame?: (input: {
54
+ source: "caller" | "assistant";
55
+ payload: string;
56
+ metadata?: Record<string, unknown>;
57
+ }) => void;
58
+ onCarrierAudioSender?: (sender: ((payload: string) => boolean) | null) => void;
59
+ isAiPaused?: () => boolean;
60
+ onAssistantTurnComplete?: () => void;
61
+ registerPlayoutIdleWaiter?: (waiter: (input?: {
62
+ timeoutMs?: number;
63
+ quietMs?: number;
64
+ }) => Promise<unknown>) => () => void;
65
+ executeTool: (input: {
66
+ toolId: string;
67
+ args: unknown;
68
+ toolCallId?: string | null;
69
+ providerCallId?: string | null;
70
+ }) => Promise<{
71
+ result: unknown;
72
+ }>;
73
+ };
74
+ export declare function summarizeTwilioPcmuSignal(base64Audio: string): import("@kognitivedev/voice-media-bridge").PcmSignalSummary;
75
+ export declare function isOpenAITwilioRealtimeReady(event: unknown, mediaProfile?: PhoneMediaProfile): boolean;
76
+ export declare function createOpenAITwilioRealtimeTools(snapshot: OpenAITwilioRealtimeSnapshot): {
77
+ type: string;
78
+ name: string;
79
+ description: string;
80
+ parameters: Record<string, unknown>;
81
+ }[];
82
+ export declare function createOpenAITwilioRealtimeSessionUpdate(snapshot: OpenAITwilioRealtimeSnapshot, model: string, defaultVoice?: string, provider?: "openai-realtime" | "xai-realtime", mediaProfile?: PhoneMediaProfile, audioRoute?: PhoneAudioRoute): {
83
+ type: string;
84
+ session: Record<string, unknown>;
85
+ };
86
+ export declare function runOpenAITwilioRealtimeBridge(input: OpenAITwilioRealtimeBridgeInput): Promise<{
87
+ sendTextTurn: (text: string) => boolean;
88
+ close: () => void;
89
+ }>;
90
+ export type XAITwilioRealtimeBridgeInput = Omit<OpenAITwilioRealtimeBridgeInput, "provider" | "realtimeUrl" | "defaultModel" | "defaultVoice" | "bridgeMode" | "supportsConversationItemTruncate">;
91
+ export declare function runXAITwilioRealtimeBridge(input: XAITwilioRealtimeBridgeInput): Promise<{
92
+ sendTextTurn: (text: string) => boolean;
93
+ close: () => void;
94
+ }>;
95
+ export {};