@framers/agentos 0.1.7 → 0.1.9
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/api/AgentOS.d.ts.map +1 -1
- package/dist/api/AgentOS.js +4 -0
- package/dist/api/AgentOS.js.map +1 -1
- package/dist/config/extension-secrets.json +162 -0
- package/dist/extensions/ExtensionManager.d.ts +8 -0
- package/dist/extensions/ExtensionManager.d.ts.map +1 -1
- package/dist/extensions/ExtensionManager.js +53 -3
- package/dist/extensions/ExtensionManager.js.map +1 -1
- package/dist/extensions/manifest.d.ts +7 -3
- package/dist/extensions/manifest.d.ts.map +1 -1
- package/dist/extensions/types.d.ts +12 -0
- package/dist/extensions/types.d.ts.map +1 -1
- package/dist/extensions/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/voice/CallManager.d.ts +116 -0
- package/dist/voice/CallManager.d.ts.map +1 -0
- package/dist/voice/CallManager.js +426 -0
- package/dist/voice/CallManager.js.map +1 -0
- package/dist/voice/IVoiceCallProvider.d.ts +137 -0
- package/dist/voice/IVoiceCallProvider.d.ts.map +1 -0
- package/dist/voice/IVoiceCallProvider.js +11 -0
- package/dist/voice/IVoiceCallProvider.js.map +1 -0
- package/dist/voice/index.d.ts +10 -0
- package/dist/voice/index.d.ts.map +1 -0
- package/dist/voice/index.js +8 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/providers/mock.d.ts +74 -0
- package/dist/voice/providers/mock.d.ts.map +1 -0
- package/dist/voice/providers/mock.js +199 -0
- package/dist/voice/providers/mock.js.map +1 -0
- package/dist/voice/telephony-audio.d.ts +42 -0
- package/dist/voice/telephony-audio.d.ts.map +1 -0
- package/dist/voice/telephony-audio.js +150 -0
- package/dist/voice/telephony-audio.js.map +1 -0
- package/dist/voice/types.d.ts +260 -0
- package/dist/voice/types.d.ts.map +1 -0
- package/dist/voice/types.js +36 -0
- package/dist/voice/types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Interface for telephony providers (Twilio, Telnyx, Plivo, etc.).
|
|
3
|
+
*
|
|
4
|
+
* Each provider implements this interface to normalize platform-specific
|
|
5
|
+
* call control, webhook handling, and audio streaming APIs into a common
|
|
6
|
+
* contract consumed by the {@link CallManager}.
|
|
7
|
+
*
|
|
8
|
+
* @module @framers/agentos/voice/IVoiceCallProvider
|
|
9
|
+
*/
|
|
10
|
+
import type { CallId, CallMode, VoiceProviderName, WebhookContext, WebhookParseResult, WebhookVerificationResult } from './types.js';
|
|
11
|
+
/** Parameters for initiating an outbound call. */
|
|
12
|
+
export interface InitiateCallInput {
|
|
13
|
+
/** Internal call ID assigned by CallManager. */
|
|
14
|
+
callId: CallId;
|
|
15
|
+
/** E.164 phone number to call from. */
|
|
16
|
+
fromNumber: string;
|
|
17
|
+
/** E.164 phone number to call. */
|
|
18
|
+
toNumber: string;
|
|
19
|
+
/** Call interaction mode. */
|
|
20
|
+
mode: CallMode;
|
|
21
|
+
/** Pre-composed message (for 'notify' mode). */
|
|
22
|
+
message?: string;
|
|
23
|
+
/** TTS voice to use for notify-mode messages. */
|
|
24
|
+
notifyVoice?: string;
|
|
25
|
+
/** Webhook URL the provider should call back to. */
|
|
26
|
+
webhookUrl: string;
|
|
27
|
+
/** Status callback URL for call state changes. */
|
|
28
|
+
statusCallbackUrl?: string;
|
|
29
|
+
/** Media stream WebSocket URL (for 'conversation' mode). */
|
|
30
|
+
mediaStreamUrl?: string;
|
|
31
|
+
/** Auth token appended to media stream URL for validation. */
|
|
32
|
+
mediaStreamToken?: string;
|
|
33
|
+
}
|
|
34
|
+
/** Result of initiating a call. */
|
|
35
|
+
export interface InitiateCallResult {
|
|
36
|
+
/** Provider-assigned call ID. */
|
|
37
|
+
providerCallId: string;
|
|
38
|
+
/** Whether the call was accepted by the provider. */
|
|
39
|
+
success: boolean;
|
|
40
|
+
/** Error message if not successful. */
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Parameters for hanging up a call. */
|
|
44
|
+
export interface HangupCallInput {
|
|
45
|
+
/** Provider-assigned call ID. */
|
|
46
|
+
providerCallId: string;
|
|
47
|
+
}
|
|
48
|
+
/** Parameters for playing TTS into a call. */
|
|
49
|
+
export interface PlayTtsInput {
|
|
50
|
+
/** Provider-assigned call ID. */
|
|
51
|
+
providerCallId: string;
|
|
52
|
+
/** Text to speak. */
|
|
53
|
+
text: string;
|
|
54
|
+
/** TTS voice name/ID. */
|
|
55
|
+
voice?: string;
|
|
56
|
+
}
|
|
57
|
+
/** Parameters for starting STT listening on a call. */
|
|
58
|
+
export interface StartListeningInput {
|
|
59
|
+
/** Provider-assigned call ID. */
|
|
60
|
+
providerCallId: string;
|
|
61
|
+
/** Language hint. */
|
|
62
|
+
language?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Parameters for stopping STT listening on a call. */
|
|
65
|
+
export interface StopListeningInput {
|
|
66
|
+
/** Provider-assigned call ID. */
|
|
67
|
+
providerCallId: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Core interface for telephony providers.
|
|
71
|
+
*
|
|
72
|
+
* Implementations wrap provider-specific SDKs (Twilio REST API, Telnyx Call
|
|
73
|
+
* Control v2, Plivo Voice API) and normalize all interactions to this contract.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* class TwilioProvider implements IVoiceCallProvider {
|
|
78
|
+
* readonly name = 'twilio';
|
|
79
|
+
*
|
|
80
|
+
* async initiateCall(input: InitiateCallInput): Promise<InitiateCallResult> {
|
|
81
|
+
* const call = await this.client.calls.create({
|
|
82
|
+
* to: input.toNumber,
|
|
83
|
+
* from: input.fromNumber,
|
|
84
|
+
* url: input.webhookUrl,
|
|
85
|
+
* statusCallback: input.statusCallbackUrl,
|
|
86
|
+
* });
|
|
87
|
+
* return { providerCallId: call.sid, success: true };
|
|
88
|
+
* }
|
|
89
|
+
* // ...
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export interface IVoiceCallProvider {
|
|
94
|
+
/** Provider identifier. */
|
|
95
|
+
readonly name: VoiceProviderName;
|
|
96
|
+
/**
|
|
97
|
+
* Verify the authenticity of an incoming webhook request.
|
|
98
|
+
* Each provider has its own signature scheme:
|
|
99
|
+
* - Twilio: HMAC-SHA1 signature header
|
|
100
|
+
* - Telnyx: Ed25519 public key verification
|
|
101
|
+
* - Plivo: HMAC-SHA256 verification
|
|
102
|
+
*/
|
|
103
|
+
verifyWebhook(ctx: WebhookContext): WebhookVerificationResult;
|
|
104
|
+
/**
|
|
105
|
+
* Parse a verified webhook payload into normalized call events.
|
|
106
|
+
* Transforms provider-specific event formats into the common
|
|
107
|
+
* {@link NormalizedCallEvent} discriminated union.
|
|
108
|
+
*/
|
|
109
|
+
parseWebhookEvent(ctx: WebhookContext): WebhookParseResult;
|
|
110
|
+
/**
|
|
111
|
+
* Initiate an outbound phone call.
|
|
112
|
+
* For 'notify' mode, the provider generates TwiML/SSML to speak the
|
|
113
|
+
* message and hang up. For 'conversation' mode, the provider sets up
|
|
114
|
+
* a bidirectional media stream.
|
|
115
|
+
*/
|
|
116
|
+
initiateCall(input: InitiateCallInput): Promise<InitiateCallResult>;
|
|
117
|
+
/**
|
|
118
|
+
* Hang up an active call.
|
|
119
|
+
*/
|
|
120
|
+
hangupCall(input: HangupCallInput): Promise<void>;
|
|
121
|
+
/**
|
|
122
|
+
* Play TTS audio into an active call (non-streaming).
|
|
123
|
+
* Used by providers that support in-call TTS via their API
|
|
124
|
+
* (e.g., Twilio's <Say> verb, Telnyx speak command).
|
|
125
|
+
*/
|
|
126
|
+
playTts?(input: PlayTtsInput): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Start STT listening on an active call (non-streaming).
|
|
129
|
+
* Used by providers that support in-call speech recognition.
|
|
130
|
+
*/
|
|
131
|
+
startListening?(input: StartListeningInput): Promise<void>;
|
|
132
|
+
/**
|
|
133
|
+
* Stop STT listening on an active call.
|
|
134
|
+
*/
|
|
135
|
+
stopListening?(input: StopListeningInput): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=IVoiceCallProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IVoiceCallProvider.d.ts","sourceRoot":"","sources":["../../src/voice/IVoiceCallProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,QAAQ,EAER,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAMpB,kDAAkD;AAClD,MAAM,WAAW,iBAAiB;IAChC,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,mCAAmC;AACnC,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uDAAuD;AACvD,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,cAAc,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAIjC;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,yBAAyB,CAAC;IAE9D;;;;OAIG;IACH,iBAAiB,CAAC,GAAG,EAAE,cAAc,GAAG,kBAAkB,CAAC;IAI3D;;;;;OAKG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAEpE;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElD;;;;OAIG;IACH,OAAO,CAAC,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C;;;OAGG;IACH,cAAc,CAAC,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,aAAa,CAAC,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Interface for telephony providers (Twilio, Telnyx, Plivo, etc.).
|
|
3
|
+
*
|
|
4
|
+
* Each provider implements this interface to normalize platform-specific
|
|
5
|
+
* call control, webhook handling, and audio streaming APIs into a common
|
|
6
|
+
* contract consumed by the {@link CallManager}.
|
|
7
|
+
*
|
|
8
|
+
* @module @framers/agentos/voice/IVoiceCallProvider
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=IVoiceCallProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IVoiceCallProvider.js","sourceRoot":"","sources":["../../src/voice/IVoiceCallProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel exports for the AgentOS Voice Call System.
|
|
3
|
+
* @module @framers/agentos/voice
|
|
4
|
+
*/
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export type { IVoiceCallProvider, InitiateCallInput, InitiateCallResult, HangupCallInput, PlayTtsInput, StartListeningInput, StopListeningInput, } from './IVoiceCallProvider.js';
|
|
7
|
+
export { CallManager } from './CallManager.js';
|
|
8
|
+
export type { CallManagerEventType, CallManagerEvent, CallManagerEventHandler, } from './CallManager.js';
|
|
9
|
+
export { convertPcmToMulaw8k, convertMulawToPcm16, escapeXml, validateE164, } from './telephony-audio.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,YAAY,CAAC;AAC3B,YAAY,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,EACT,YAAY,GACb,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Barrel exports for the AgentOS Voice Call System.
|
|
3
|
+
* @module @framers/agentos/voice
|
|
4
|
+
*/
|
|
5
|
+
export * from './types.js';
|
|
6
|
+
export { CallManager } from './CallManager.js';
|
|
7
|
+
export { convertPcmToMulaw8k, convertMulawToPcm16, escapeXml, validateE164, } from './telephony-audio.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/voice/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,YAAY,CAAC;AAU3B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAM/C,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,EACT,YAAY,GACb,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mock voice call provider for development and testing.
|
|
3
|
+
*
|
|
4
|
+
* Simulates the full call lifecycle (initiated -> ringing -> answered -> active -> completed)
|
|
5
|
+
* using in-memory state and setTimeout-driven state progression. No external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @module @framers/agentos/voice/providers/mock
|
|
8
|
+
*/
|
|
9
|
+
import type { IVoiceCallProvider, InitiateCallInput, InitiateCallResult, HangupCallInput, PlayTtsInput } from '../IVoiceCallProvider.js';
|
|
10
|
+
import type { VoiceCallConfig, NormalizedCallEvent, WebhookContext, WebhookVerificationResult, WebhookParseResult } from '../types.js';
|
|
11
|
+
/**
|
|
12
|
+
* A mock IVoiceCallProvider that simulates call lifecycle in-memory.
|
|
13
|
+
*
|
|
14
|
+
* State progression after initiateCall():
|
|
15
|
+
* - 100ms: ringing
|
|
16
|
+
* - 300ms: answered
|
|
17
|
+
* - 500ms: active (call-completed is NOT auto-emitted; call stays active until hangup)
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { MockVoiceProvider } from './providers/mock.js';
|
|
22
|
+
* import { CallManager } from '../CallManager.js';
|
|
23
|
+
*
|
|
24
|
+
* const provider = new MockVoiceProvider();
|
|
25
|
+
* const manager = new CallManager(provider);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class MockVoiceProvider implements IVoiceCallProvider {
|
|
29
|
+
readonly name: "mock";
|
|
30
|
+
private calls;
|
|
31
|
+
private eventHandler?;
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the mock provider. No-op since there is no external service.
|
|
34
|
+
*/
|
|
35
|
+
initialize(_config: VoiceCallConfig): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Shut down the mock provider -- clears all in-flight calls and timers.
|
|
38
|
+
*/
|
|
39
|
+
shutdown(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Register an event handler. The CallManager calls this to receive
|
|
42
|
+
* normalized events from the provider.
|
|
43
|
+
*/
|
|
44
|
+
onEvent(handler: (event: NormalizedCallEvent) => void): void;
|
|
45
|
+
/**
|
|
46
|
+
* Verify a webhook request. Always returns valid for mock provider.
|
|
47
|
+
*/
|
|
48
|
+
verifyWebhook(_ctx: WebhookContext): WebhookVerificationResult;
|
|
49
|
+
/**
|
|
50
|
+
* Parse a webhook payload. Returns no events since the mock provider
|
|
51
|
+
* does not receive real webhooks.
|
|
52
|
+
*/
|
|
53
|
+
parseWebhookEvent(_ctx: WebhookContext): WebhookParseResult;
|
|
54
|
+
/**
|
|
55
|
+
* Initiate a simulated outbound call. The call progresses through
|
|
56
|
+
* ringing -> answered -> active on short timers.
|
|
57
|
+
*/
|
|
58
|
+
initiateCall(input: InitiateCallInput): Promise<InitiateCallResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Hang up a simulated call. Emits a call-completed event and removes
|
|
61
|
+
* the call from the in-memory store.
|
|
62
|
+
*/
|
|
63
|
+
hangupCall(input: HangupCallInput): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Simulate TTS playback. Briefly transitions the call to 'speaking' state
|
|
66
|
+
* then back to 'active' after a short delay.
|
|
67
|
+
*/
|
|
68
|
+
playTts(input: PlayTtsInput): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Advance a call to a new state and emit the corresponding normalized event.
|
|
71
|
+
*/
|
|
72
|
+
private emitStateEvent;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../../src/voice/providers/mock.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,YAAY,EACb,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAiCrB;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,iBAAkB,YAAW,kBAAkB;IAC1D,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;IAEhC,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,YAAY,CAAC,CAAuC;IAI5D;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAY/B;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IAM5D;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,cAAc,GAAG,yBAAyB;IAI9D;;;OAGG;IACH,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,kBAAkB;IAM3D;;;OAGG;IACG,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA4BzE;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBvD;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAejD;;OAEG;IACH,OAAO,CAAC,cAAc;CAoDvB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mock voice call provider for development and testing.
|
|
3
|
+
*
|
|
4
|
+
* Simulates the full call lifecycle (initiated -> ringing -> answered -> active -> completed)
|
|
5
|
+
* using in-memory state and setTimeout-driven state progression. No external dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @module @framers/agentos/voice/providers/mock
|
|
8
|
+
*/
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
let callCounter = 0;
|
|
13
|
+
function nextCallId() {
|
|
14
|
+
return `mock-call-${++callCounter}`;
|
|
15
|
+
}
|
|
16
|
+
function nextEventId() {
|
|
17
|
+
return `mock-evt-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
18
|
+
}
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// MockVoiceProvider
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* A mock IVoiceCallProvider that simulates call lifecycle in-memory.
|
|
24
|
+
*
|
|
25
|
+
* State progression after initiateCall():
|
|
26
|
+
* - 100ms: ringing
|
|
27
|
+
* - 300ms: answered
|
|
28
|
+
* - 500ms: active (call-completed is NOT auto-emitted; call stays active until hangup)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { MockVoiceProvider } from './providers/mock.js';
|
|
33
|
+
* import { CallManager } from '../CallManager.js';
|
|
34
|
+
*
|
|
35
|
+
* const provider = new MockVoiceProvider();
|
|
36
|
+
* const manager = new CallManager(provider);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export class MockVoiceProvider {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.name = 'mock';
|
|
42
|
+
this.calls = new Map();
|
|
43
|
+
}
|
|
44
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the mock provider. No-op since there is no external service.
|
|
47
|
+
*/
|
|
48
|
+
async initialize(_config) {
|
|
49
|
+
// Nothing to initialize for the mock provider.
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Shut down the mock provider -- clears all in-flight calls and timers.
|
|
53
|
+
*/
|
|
54
|
+
async shutdown() {
|
|
55
|
+
for (const call of this.calls.values()) {
|
|
56
|
+
for (const timer of call.timers) {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
this.calls.clear();
|
|
61
|
+
this.eventHandler = undefined;
|
|
62
|
+
}
|
|
63
|
+
// ── Event subscription ───────────────────────────────────────────────────
|
|
64
|
+
/**
|
|
65
|
+
* Register an event handler. The CallManager calls this to receive
|
|
66
|
+
* normalized events from the provider.
|
|
67
|
+
*/
|
|
68
|
+
onEvent(handler) {
|
|
69
|
+
this.eventHandler = handler;
|
|
70
|
+
}
|
|
71
|
+
// ── Webhook ──────────────────────────────────────────────────────────────
|
|
72
|
+
/**
|
|
73
|
+
* Verify a webhook request. Always returns valid for mock provider.
|
|
74
|
+
*/
|
|
75
|
+
verifyWebhook(_ctx) {
|
|
76
|
+
return { valid: true };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Parse a webhook payload. Returns no events since the mock provider
|
|
80
|
+
* does not receive real webhooks.
|
|
81
|
+
*/
|
|
82
|
+
parseWebhookEvent(_ctx) {
|
|
83
|
+
return { events: [] };
|
|
84
|
+
}
|
|
85
|
+
// ── Call Control ─────────────────────────────────────────────────────────
|
|
86
|
+
/**
|
|
87
|
+
* Initiate a simulated outbound call. The call progresses through
|
|
88
|
+
* ringing -> answered -> active on short timers.
|
|
89
|
+
*/
|
|
90
|
+
async initiateCall(input) {
|
|
91
|
+
const providerCallId = nextCallId();
|
|
92
|
+
const record = {
|
|
93
|
+
providerCallId,
|
|
94
|
+
callId: input.callId,
|
|
95
|
+
from: input.fromNumber,
|
|
96
|
+
to: input.toNumber,
|
|
97
|
+
state: 'initiated',
|
|
98
|
+
timers: [],
|
|
99
|
+
};
|
|
100
|
+
this.calls.set(providerCallId, record);
|
|
101
|
+
// Simulate state progression
|
|
102
|
+
record.timers.push(setTimeout(() => this.emitStateEvent(providerCallId, 'ringing'), 100));
|
|
103
|
+
record.timers.push(setTimeout(() => this.emitStateEvent(providerCallId, 'answered'), 300));
|
|
104
|
+
record.timers.push(setTimeout(() => this.emitStateEvent(providerCallId, 'active'), 500));
|
|
105
|
+
return { providerCallId, success: true };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Hang up a simulated call. Emits a call-completed event and removes
|
|
109
|
+
* the call from the in-memory store.
|
|
110
|
+
*/
|
|
111
|
+
async hangupCall(input) {
|
|
112
|
+
const record = this.calls.get(input.providerCallId);
|
|
113
|
+
if (!record)
|
|
114
|
+
return;
|
|
115
|
+
// Clear any pending state-progression timers
|
|
116
|
+
for (const timer of record.timers) {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
}
|
|
119
|
+
record.timers = [];
|
|
120
|
+
// Emit hangup and completed events
|
|
121
|
+
record.state = 'hangup-bot';
|
|
122
|
+
this.eventHandler?.({
|
|
123
|
+
eventId: nextEventId(),
|
|
124
|
+
providerCallId: input.providerCallId,
|
|
125
|
+
kind: 'call-completed',
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
duration: Math.floor((Date.now() - 0) / 1000),
|
|
128
|
+
});
|
|
129
|
+
this.calls.delete(input.providerCallId);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Simulate TTS playback. Briefly transitions the call to 'speaking' state
|
|
133
|
+
* then back to 'active' after a short delay.
|
|
134
|
+
*/
|
|
135
|
+
async playTts(input) {
|
|
136
|
+
const record = this.calls.get(input.providerCallId);
|
|
137
|
+
if (!record)
|
|
138
|
+
return;
|
|
139
|
+
// Briefly enter 'speaking' state
|
|
140
|
+
record.state = 'speaking';
|
|
141
|
+
// Return to 'active' after simulated speech duration
|
|
142
|
+
record.timers.push(setTimeout(() => this.emitStateEvent(input.providerCallId, 'active'), 200));
|
|
143
|
+
}
|
|
144
|
+
// ── Internal ─────────────────────────────────────────────────────────────
|
|
145
|
+
/**
|
|
146
|
+
* Advance a call to a new state and emit the corresponding normalized event.
|
|
147
|
+
*/
|
|
148
|
+
emitStateEvent(providerCallId, newState) {
|
|
149
|
+
const record = this.calls.get(providerCallId);
|
|
150
|
+
if (!record)
|
|
151
|
+
return;
|
|
152
|
+
record.state = newState;
|
|
153
|
+
const base = {
|
|
154
|
+
eventId: nextEventId(),
|
|
155
|
+
providerCallId,
|
|
156
|
+
timestamp: Date.now(),
|
|
157
|
+
};
|
|
158
|
+
switch (newState) {
|
|
159
|
+
case 'ringing':
|
|
160
|
+
this.eventHandler?.({ ...base, kind: 'call-ringing' });
|
|
161
|
+
break;
|
|
162
|
+
case 'answered':
|
|
163
|
+
this.eventHandler?.({ ...base, kind: 'call-answered' });
|
|
164
|
+
break;
|
|
165
|
+
case 'active':
|
|
166
|
+
// 'active' is an internal state; no specific normalized event kind for it.
|
|
167
|
+
// We treat transition to active as call-answered if not already sent.
|
|
168
|
+
break;
|
|
169
|
+
case 'completed':
|
|
170
|
+
this.eventHandler?.({ ...base, kind: 'call-completed' });
|
|
171
|
+
break;
|
|
172
|
+
case 'hangup-user':
|
|
173
|
+
this.eventHandler?.({ ...base, kind: 'call-hangup-user' });
|
|
174
|
+
break;
|
|
175
|
+
case 'hangup-bot':
|
|
176
|
+
this.eventHandler?.({ ...base, kind: 'call-completed' });
|
|
177
|
+
break;
|
|
178
|
+
case 'failed':
|
|
179
|
+
this.eventHandler?.({ ...base, kind: 'call-failed', reason: 'mock failure' });
|
|
180
|
+
break;
|
|
181
|
+
case 'error':
|
|
182
|
+
this.eventHandler?.({ ...base, kind: 'call-error', error: 'mock error' });
|
|
183
|
+
break;
|
|
184
|
+
case 'busy':
|
|
185
|
+
this.eventHandler?.({ ...base, kind: 'call-busy' });
|
|
186
|
+
break;
|
|
187
|
+
case 'no-answer':
|
|
188
|
+
this.eventHandler?.({ ...base, kind: 'call-no-answer' });
|
|
189
|
+
break;
|
|
190
|
+
case 'voicemail':
|
|
191
|
+
this.eventHandler?.({ ...base, kind: 'call-voicemail' });
|
|
192
|
+
break;
|
|
193
|
+
default:
|
|
194
|
+
// speaking / listening / initiated -- internal states with no webhook event
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=mock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock.js","sourceRoot":"","sources":["../../../src/voice/providers/mock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Telephony audio utilities.
|
|
3
|
+
*
|
|
4
|
+
* Phone networks use mu-law encoding at 8kHz mono. This module provides
|
|
5
|
+
* conversion utilities for bridging between PCM audio (from TTS providers)
|
|
6
|
+
* and the mu-law format required by telephony media streams.
|
|
7
|
+
*
|
|
8
|
+
* @module @framers/agentos/voice/telephony-audio
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Convert PCM audio buffer to mu-law 8kHz mono format for telephony.
|
|
12
|
+
*
|
|
13
|
+
* @param pcmBuffer - Raw PCM audio data (signed 16-bit little-endian).
|
|
14
|
+
* @param sampleRate - Sample rate of the input PCM data.
|
|
15
|
+
* @returns Buffer of mu-law encoded audio at 8kHz mono.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* // TTS returns 24kHz PCM
|
|
20
|
+
* const ttsAudio = await ttsProvider.synthesize("Hello");
|
|
21
|
+
* const phoneAudio = convertPcmToMulaw8k(ttsAudio, 24000);
|
|
22
|
+
* mediaStream.sendAudio(streamSid, phoneAudio);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function convertPcmToMulaw8k(pcmBuffer: Buffer, sampleRate: number): Buffer;
|
|
26
|
+
/**
|
|
27
|
+
* Convert mu-law 8kHz audio to PCM signed 16-bit LE.
|
|
28
|
+
*
|
|
29
|
+
* @param mulawBuffer - Mu-law encoded audio data.
|
|
30
|
+
* @returns Buffer of PCM signed 16-bit little-endian audio.
|
|
31
|
+
*/
|
|
32
|
+
export declare function convertMulawToPcm16(mulawBuffer: Buffer): Buffer;
|
|
33
|
+
/**
|
|
34
|
+
* Escape XML special characters for TwiML/VoiceXML generation.
|
|
35
|
+
*/
|
|
36
|
+
export declare function escapeXml(text: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Validate an E.164 phone number format.
|
|
39
|
+
* @returns The normalized number, or null if invalid.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateE164(number: string): string | null;
|
|
42
|
+
//# sourceMappingURL=telephony-audio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telephony-audio.d.ts","sourceRoot":"","sources":["../../src/voice/telephony-audio.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6CH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAkBjF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAU/D;AAqDD;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG1D"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Telephony audio utilities.
|
|
3
|
+
*
|
|
4
|
+
* Phone networks use mu-law encoding at 8kHz mono. This module provides
|
|
5
|
+
* conversion utilities for bridging between PCM audio (from TTS providers)
|
|
6
|
+
* and the mu-law format required by telephony media streams.
|
|
7
|
+
*
|
|
8
|
+
* @module @framers/agentos/voice/telephony-audio
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Mu-law Encoding Table
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const MULAW_BIAS = 0x84;
|
|
14
|
+
const MULAW_CLIP = 32635;
|
|
15
|
+
/** Pre-computed linear-to-mu-law compression table for fast encoding. */
|
|
16
|
+
const encodeTable = new Uint8Array(65536);
|
|
17
|
+
function buildEncodeTable() {
|
|
18
|
+
for (let i = 0; i < 65536; i++) {
|
|
19
|
+
// Convert unsigned 16-bit to signed
|
|
20
|
+
let sample = i < 32768 ? i : i - 65536;
|
|
21
|
+
// Determine sign
|
|
22
|
+
const sign = sample < 0 ? 0x80 : 0;
|
|
23
|
+
if (sample < 0)
|
|
24
|
+
sample = -sample;
|
|
25
|
+
// Clip
|
|
26
|
+
if (sample > MULAW_CLIP)
|
|
27
|
+
sample = MULAW_CLIP;
|
|
28
|
+
sample += MULAW_BIAS;
|
|
29
|
+
// Find segment
|
|
30
|
+
let exponent = 7;
|
|
31
|
+
let mask = 0x4000;
|
|
32
|
+
while (!(sample & mask) && exponent > 0) {
|
|
33
|
+
exponent--;
|
|
34
|
+
mask >>= 1;
|
|
35
|
+
}
|
|
36
|
+
const mantissa = (sample >> (exponent + 3)) & 0x0f;
|
|
37
|
+
const muLawByte = ~(sign | (exponent << 4) | mantissa) & 0xff;
|
|
38
|
+
encodeTable[i] = muLawByte;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
buildEncodeTable();
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// PCM to Mu-law Conversion
|
|
44
|
+
// ============================================================================
|
|
45
|
+
/**
|
|
46
|
+
* Convert PCM audio buffer to mu-law 8kHz mono format for telephony.
|
|
47
|
+
*
|
|
48
|
+
* @param pcmBuffer - Raw PCM audio data (signed 16-bit little-endian).
|
|
49
|
+
* @param sampleRate - Sample rate of the input PCM data.
|
|
50
|
+
* @returns Buffer of mu-law encoded audio at 8kHz mono.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* // TTS returns 24kHz PCM
|
|
55
|
+
* const ttsAudio = await ttsProvider.synthesize("Hello");
|
|
56
|
+
* const phoneAudio = convertPcmToMulaw8k(ttsAudio, 24000);
|
|
57
|
+
* mediaStream.sendAudio(streamSid, phoneAudio);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function convertPcmToMulaw8k(pcmBuffer, sampleRate) {
|
|
61
|
+
// Step 1: Resample to 8kHz if needed
|
|
62
|
+
const resampled = sampleRate !== 8000
|
|
63
|
+
? resamplePcm16(pcmBuffer, sampleRate, 8000)
|
|
64
|
+
: pcmBuffer;
|
|
65
|
+
// Step 2: Encode to mu-law
|
|
66
|
+
const numSamples = resampled.length / 2; // 16-bit samples
|
|
67
|
+
const output = Buffer.alloc(numSamples);
|
|
68
|
+
for (let i = 0; i < numSamples; i++) {
|
|
69
|
+
const sample = resampled.readInt16LE(i * 2);
|
|
70
|
+
// Convert signed to unsigned index for table lookup
|
|
71
|
+
const unsignedSample = sample < 0 ? sample + 65536 : sample;
|
|
72
|
+
output[i] = encodeTable[unsignedSample];
|
|
73
|
+
}
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Convert mu-law 8kHz audio to PCM signed 16-bit LE.
|
|
78
|
+
*
|
|
79
|
+
* @param mulawBuffer - Mu-law encoded audio data.
|
|
80
|
+
* @returns Buffer of PCM signed 16-bit little-endian audio.
|
|
81
|
+
*/
|
|
82
|
+
export function convertMulawToPcm16(mulawBuffer) {
|
|
83
|
+
const output = Buffer.alloc(mulawBuffer.length * 2);
|
|
84
|
+
for (let i = 0; i < mulawBuffer.length; i++) {
|
|
85
|
+
const muLaw = mulawBuffer[i];
|
|
86
|
+
const sample = decodeMulawSample(muLaw);
|
|
87
|
+
output.writeInt16LE(sample, i * 2);
|
|
88
|
+
}
|
|
89
|
+
return output;
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Resampling
|
|
93
|
+
// ============================================================================
|
|
94
|
+
/**
|
|
95
|
+
* Simple linear-interpolation resampler for 16-bit PCM audio.
|
|
96
|
+
* Not studio quality, but sufficient for voice telephony.
|
|
97
|
+
*/
|
|
98
|
+
function resamplePcm16(input, fromRate, toRate) {
|
|
99
|
+
const numInputSamples = input.length / 2;
|
|
100
|
+
const ratio = fromRate / toRate;
|
|
101
|
+
const numOutputSamples = Math.floor(numInputSamples / ratio);
|
|
102
|
+
const output = Buffer.alloc(numOutputSamples * 2);
|
|
103
|
+
for (let i = 0; i < numOutputSamples; i++) {
|
|
104
|
+
const srcIndex = i * ratio;
|
|
105
|
+
const srcFloor = Math.floor(srcIndex);
|
|
106
|
+
const srcCeil = Math.min(srcFloor + 1, numInputSamples - 1);
|
|
107
|
+
const frac = srcIndex - srcFloor;
|
|
108
|
+
const s0 = input.readInt16LE(srcFloor * 2);
|
|
109
|
+
const s1 = input.readInt16LE(srcCeil * 2);
|
|
110
|
+
const sample = Math.round(s0 + (s1 - s0) * frac);
|
|
111
|
+
output.writeInt16LE(Math.max(-32768, Math.min(32767, sample)), i * 2);
|
|
112
|
+
}
|
|
113
|
+
return output;
|
|
114
|
+
}
|
|
115
|
+
// ============================================================================
|
|
116
|
+
// Mu-law Decode
|
|
117
|
+
// ============================================================================
|
|
118
|
+
/** Decode a single mu-law byte to a signed 16-bit PCM sample. */
|
|
119
|
+
function decodeMulawSample(muLaw) {
|
|
120
|
+
const complement = ~muLaw & 0xff;
|
|
121
|
+
const sign = complement & 0x80;
|
|
122
|
+
const exponent = (complement >> 4) & 0x07;
|
|
123
|
+
const mantissa = complement & 0x0f;
|
|
124
|
+
let sample = ((mantissa << 3) + MULAW_BIAS) << exponent;
|
|
125
|
+
sample -= MULAW_BIAS;
|
|
126
|
+
return sign ? -sample : sample;
|
|
127
|
+
}
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Utilities
|
|
130
|
+
// ============================================================================
|
|
131
|
+
/**
|
|
132
|
+
* Escape XML special characters for TwiML/VoiceXML generation.
|
|
133
|
+
*/
|
|
134
|
+
export function escapeXml(text) {
|
|
135
|
+
return text
|
|
136
|
+
.replace(/&/g, '&')
|
|
137
|
+
.replace(/</g, '<')
|
|
138
|
+
.replace(/>/g, '>')
|
|
139
|
+
.replace(/"/g, '"')
|
|
140
|
+
.replace(/'/g, ''');
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Validate an E.164 phone number format.
|
|
144
|
+
* @returns The normalized number, or null if invalid.
|
|
145
|
+
*/
|
|
146
|
+
export function validateE164(number) {
|
|
147
|
+
const normalized = number.replace(/[\s\-()]/g, '');
|
|
148
|
+
return /^\+[1-9]\d{1,14}$/.test(normalized) ? normalized : null;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=telephony-audio.js.map
|