@recombine-ai/platform 0.1.0 → 0.1.1
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/build/context-cache.d.ts +17 -0
- package/build/context-cache.d.ts.map +1 -0
- package/build/context-cache.js +68 -0
- package/build/index.d.ts +4 -5
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -0
- package/build/interfaces/platform.d.ts +39 -0
- package/build/interfaces/platform.d.ts.map +1 -0
- package/build/interfaces/platform.js +3 -0
- package/build/interfaces/rc-voice.d.ts +53 -0
- package/build/interfaces/rc-voice.d.ts.map +1 -0
- package/build/interfaces/rc-voice.js +1 -0
- package/build/timeouts-manager.d.ts +23 -0
- package/build/timeouts-manager.d.ts.map +1 -0
- package/build/timeouts-manager.js +99 -0
- package/package.json +8 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { RedisClientType } from 'redis';
|
|
2
|
+
export declare function createInMemoryCache<TContext extends object>(): {
|
|
3
|
+
getContext: {
|
|
4
|
+
(callId: string): Promise<TContext>;
|
|
5
|
+
(callId: string, defaultContext: TContext): Promise<TContext>;
|
|
6
|
+
};
|
|
7
|
+
setContext(callId: string, context: TContext): Promise<void>;
|
|
8
|
+
clearContext(callId: string): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export declare function createRedisCache<TContext extends object>(client: RedisClientType, options?: RedisCacheOptions): ContextCache<TContext>;
|
|
11
|
+
export type ContextCache<TContext extends object> = ReturnType<typeof createInMemoryCache<TContext>>;
|
|
12
|
+
export type RedisCacheOptions = {
|
|
13
|
+
keyPrefix?: string;
|
|
14
|
+
ttlSeconds?: number;
|
|
15
|
+
prolongOnGet?: boolean;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=context-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-cache.d.ts","sourceRoot":"","sources":["../src/context-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAc,MAAM,OAAO,CAAA;AAEnD,wBAAgB,mBAAmB,CAAC,QAAQ,SAAS,MAAM;;iBAGrB,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;iBAC1B,MAAM,kBAAkB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;;uBAazD,MAAM,WAAW,QAAQ;yBAGvB,MAAM;EAIxC;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,SAAS,MAAM,EACpD,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,iBAA+C,GACzD,YAAY,CAAC,QAAQ,CAAC,CAsExB;AAED,MAAM,MAAM,YAAY,CAAC,QAAQ,SAAS,MAAM,IAAI,UAAU,CAAC,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAA;AAMpG,MAAM,MAAM,iBAAiB,GAAG;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function createInMemoryCache() {
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
async function getContext(callId, defaultContext) {
|
|
4
|
+
if (cache.has(callId)) {
|
|
5
|
+
return cache.get(callId);
|
|
6
|
+
}
|
|
7
|
+
else if (defaultContext) {
|
|
8
|
+
const context = clone(defaultContext);
|
|
9
|
+
cache.set(callId, context);
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
throw new Error(`Cannot find context for ${callId}`);
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
getContext,
|
|
16
|
+
async setContext(callId, context) {
|
|
17
|
+
cache.set(callId, context);
|
|
18
|
+
},
|
|
19
|
+
async clearContext(callId) {
|
|
20
|
+
cache.delete(callId);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function createRedisCache(client, options = DEFAULT_REDIS_CACHE_OPTIONS) {
|
|
25
|
+
function createKey(callId) {
|
|
26
|
+
return options.keyPrefix + callId;
|
|
27
|
+
}
|
|
28
|
+
async function getContext(callId, defaultContext) {
|
|
29
|
+
const data = await client.get(createKey(callId));
|
|
30
|
+
if (data) {
|
|
31
|
+
if (options.prolongOnGet && options.ttlSeconds) {
|
|
32
|
+
await client.expire(createKey(callId), options.ttlSeconds);
|
|
33
|
+
}
|
|
34
|
+
return JSON.parse(data);
|
|
35
|
+
}
|
|
36
|
+
if (defaultContext) {
|
|
37
|
+
await setContext(callId, defaultContext);
|
|
38
|
+
return clone(defaultContext);
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`Cannot find context for ${callId}`);
|
|
41
|
+
}
|
|
42
|
+
async function setContext(callId, context) {
|
|
43
|
+
const key = createKey(callId);
|
|
44
|
+
const value = JSON.stringify(context);
|
|
45
|
+
const setOptions = {};
|
|
46
|
+
if (options.ttlSeconds) {
|
|
47
|
+
setOptions.expiration = { type: 'EX', value: options.ttlSeconds };
|
|
48
|
+
}
|
|
49
|
+
await client.set(key, value, setOptions);
|
|
50
|
+
}
|
|
51
|
+
async function clearContext(callId) {
|
|
52
|
+
const key = createKey(callId);
|
|
53
|
+
await client.del(key);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
getContext,
|
|
57
|
+
setContext,
|
|
58
|
+
clearContext,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function clone(obj) {
|
|
62
|
+
return JSON.parse(JSON.stringify(obj));
|
|
63
|
+
}
|
|
64
|
+
const DEFAULT_REDIS_CACHE_OPTIONS = {
|
|
65
|
+
keyPrefix: 'agent:context:',
|
|
66
|
+
ttlSeconds: 900,
|
|
67
|
+
prolongOnGet: true,
|
|
68
|
+
};
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
export * from './interfaces/
|
|
2
|
-
export * from './interfaces/
|
|
3
|
-
export * from './context';
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './stubs';
|
|
1
|
+
export * from './interfaces/platform';
|
|
2
|
+
export * from './interfaces/rc-voice';
|
|
3
|
+
export * from './context-cache';
|
|
4
|
+
export * from './timeouts-manager';
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ReadableStream } from 'node:stream/web';
|
|
2
|
+
import { AIEngine, AIStreamEngine, LlmAdapter, LlmStreamAdapter, Logger, Message, PromptFS, ResponseChunk } from '@recombine-ai/engine';
|
|
3
|
+
import { ContextCache } from '../context-cache';
|
|
4
|
+
import { TimeoutManager } from '../timeouts-manager';
|
|
5
|
+
import { IRcVoice } from './rc-voice';
|
|
6
|
+
export interface IModels {
|
|
7
|
+
models: Record<string, LlmAdapter>;
|
|
8
|
+
streamingModels: Record<string, LlmStreamAdapter>;
|
|
9
|
+
}
|
|
10
|
+
export interface Platform<TContext extends {}, TModels extends IModels> {
|
|
11
|
+
streamAi: AIStreamEngine<TContext>;
|
|
12
|
+
ai: AIEngine<TContext>;
|
|
13
|
+
fs: PromptFS;
|
|
14
|
+
rcVoice: IRcVoice;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
timeoutManager: TimeoutManager;
|
|
17
|
+
models: TModels['models'];
|
|
18
|
+
streamingModels: TModels['streamingModels'];
|
|
19
|
+
contextCache: ContextCache<TContext>;
|
|
20
|
+
}
|
|
21
|
+
export interface CallInfo {
|
|
22
|
+
callId: string;
|
|
23
|
+
phoneNumber?: string;
|
|
24
|
+
headers: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
export interface VoiceAgent {
|
|
27
|
+
onCallStart: (call: CallInfo) => Promise<{
|
|
28
|
+
initialMessage: string;
|
|
29
|
+
initialSettings?: Record<string, string | number>;
|
|
30
|
+
}>;
|
|
31
|
+
onCallReady: (call: CallInfo) => Promise<void>;
|
|
32
|
+
streamResponse: (messages: Message[], call: CallInfo) => Promise<ReadableStream<ResponseChunk>>;
|
|
33
|
+
onCallEnd: (call: CallInfo) => Promise<void>;
|
|
34
|
+
onAgentSpeechDone: (messages: Message[], call: CallInfo) => Promise<void>;
|
|
35
|
+
onServiceStartup: () => Promise<void>;
|
|
36
|
+
onFatalError: (callId: string, err: any) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export declare function createAgent<TContext extends {}, TServices, TModels extends IModels>(creator: (platform: Platform<TContext, TModels>, services: TServices) => VoiceAgent): (platform: Platform<TContext, TModels>, services: TServices) => VoiceAgent;
|
|
39
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/interfaces/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EACH,QAAQ,EACR,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,EAChB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAClC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACpD;AAED,MAAM,WAAW,QAAQ,CAAC,QAAQ,SAAS,EAAE,EAAE,OAAO,SAAS,OAAO;IAElE,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAElC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAEtB,EAAE,EAAE,QAAQ,CAAA;IAEZ,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;IAE9B,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACzB,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAE3C,YAAY,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;CACvC;AAED,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,UAAU;IACvB,WAAW,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC;QACrC,cAAc,EAAE,MAAM,CAAA;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;KACpD,CAAC,CAAA;IACF,WAAW,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAA;IAC/F,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzE,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5D;AAED,wBAAgB,WAAW,CAAC,QAAQ,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,SAAS,OAAO,EAC/E,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,SAAS,KAAK,UAAU,cAA/D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,SAAS,KAAK,UAAU,CAGtF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Message } from '@recombine-ai/engine';
|
|
2
|
+
export interface IRcVoice {
|
|
3
|
+
setVoiceSetting(voiceSettings: Record<string, string | number>): Promise<void>;
|
|
4
|
+
setUpSip(agentName: string, trunks: TrunksCredentials): Promise<void>;
|
|
5
|
+
steer(callId: string, payload: SteerPayload): Promise<void>;
|
|
6
|
+
freeze(callId: string): Promise<void>;
|
|
7
|
+
unfreeze(callId: string): Promise<void>;
|
|
8
|
+
addMessage(callId: string, message: Message): Promise<void>;
|
|
9
|
+
reload(callId: string): Promise<void>;
|
|
10
|
+
transfer(agentName: string, destination: string, callId: string, disclaimer?: string, headers?: Record<string, string>): Promise<void>;
|
|
11
|
+
hangup(callId: string): Promise<void>;
|
|
12
|
+
getCallDetails(callId: string): Promise<CallDetails>;
|
|
13
|
+
}
|
|
14
|
+
export type TrunksCredentials = any;
|
|
15
|
+
export interface SteerPayload {
|
|
16
|
+
respond?: boolean;
|
|
17
|
+
switchToAgent?: string;
|
|
18
|
+
addMessages?: {
|
|
19
|
+
sender: 'user' | 'agent' | 'system';
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
ignoreWhen?: 'agentSpeaks' | 'userSpeaks' | 'anyoneSpeaks';
|
|
23
|
+
}
|
|
24
|
+
export interface VoiceAgentConnector {
|
|
25
|
+
setUpSip(): Promise<void>;
|
|
26
|
+
freeze(callId: string): Promise<void>;
|
|
27
|
+
unfreeze(callId: string): Promise<void>;
|
|
28
|
+
addMessage(callId: string, message: Message): Promise<void>;
|
|
29
|
+
setVoiceSettings(settings: Record<string, string | number>): Promise<void>;
|
|
30
|
+
reload(callId: string): Promise<void>;
|
|
31
|
+
transfer(destination: string, callId: string, headers?: Record<string, string>, reason?: TransferReason, disclaimer?: string, waitBefore?: number): Promise<void>;
|
|
32
|
+
hangup(callId: string, waitBefore?: number): Promise<void>;
|
|
33
|
+
getCallDetails(callId: string): Promise<CallDetails | null>;
|
|
34
|
+
}
|
|
35
|
+
export type TransferReason = 'escalation' | 'error';
|
|
36
|
+
export interface CallDetails {
|
|
37
|
+
callId: string;
|
|
38
|
+
callStart: string;
|
|
39
|
+
callEnd: string;
|
|
40
|
+
error: null | string;
|
|
41
|
+
recording: null | string;
|
|
42
|
+
callDuration: number;
|
|
43
|
+
endedReason: string;
|
|
44
|
+
transcript: TranscriptItem[];
|
|
45
|
+
messages: Message[];
|
|
46
|
+
}
|
|
47
|
+
export interface TranscriptItem {
|
|
48
|
+
text: string;
|
|
49
|
+
role: 'User' | 'Agent';
|
|
50
|
+
time: string;
|
|
51
|
+
duration: number;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=rc-voice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rc-voice.d.ts","sourceRoot":"","sources":["../../src/interfaces/rc-voice.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Logger } from '@recombine-ai/engine';
|
|
2
|
+
import { ContextCache } from './context-cache';
|
|
3
|
+
export type TimeoutsState = {
|
|
4
|
+
timeouts: Record<string, number>;
|
|
5
|
+
};
|
|
6
|
+
type CallTimeoutsRecord = {
|
|
7
|
+
id: number;
|
|
8
|
+
timer: NodeJS.Timeout;
|
|
9
|
+
};
|
|
10
|
+
export declare class TimeoutManager {
|
|
11
|
+
readonly timeoutsCache: ContextCache<TimeoutsState>;
|
|
12
|
+
readonly logger: Logger;
|
|
13
|
+
private shallSkip;
|
|
14
|
+
readonly activeLocalTimeouts: Map<string, Record<string, CallTimeoutsRecord>>;
|
|
15
|
+
constructor(timeoutsCache: ContextCache<TimeoutsState>, logger: Logger, shallSkip?: boolean);
|
|
16
|
+
getLocalTimeouts(callId: string): Record<string, CallTimeoutsRecord>;
|
|
17
|
+
getActiveTimeouts(callId: string): Promise<Record<string, number>>;
|
|
18
|
+
deleteTimeout(callId: string, key: string, timeoutId?: number): Promise<void>;
|
|
19
|
+
setTimeout(callId: string, key: string, cb: () => Promise<void> | void, timeout: number): Promise<void>;
|
|
20
|
+
clearAllTimeouts(callId: string): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=timeouts-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeouts-manager.d.ts","sourceRoot":"","sources":["../src/timeouts-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAK9C,MAAM,MAAM,aAAa,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC,CAAA;AAKD,KAAK,kBAAkB,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAC,OAAO,CAAA;CACxB,CAAA;AAED,qBAAa,cAAc;IAOnB,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC,aAAa,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM;IAEvB,OAAO,CAAC,SAAS;IANrB,QAAQ,CAAC,mBAAmB,kDAAwD;gBAGvE,aAAa,EAAE,YAAY,CAAC,aAAa,CAAC,EAC1C,MAAM,EAAE,MAAM,EAEf,SAAS,UAAQ;IAQ7B,gBAAgB,CAAC,MAAM,EAAE,MAAM;IAYzB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAWlE,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IA4B7D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM;IAmEvF,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYxD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export class TimeoutManager {
|
|
2
|
+
timeoutsCache;
|
|
3
|
+
logger;
|
|
4
|
+
shallSkip;
|
|
5
|
+
activeLocalTimeouts = new Map();
|
|
6
|
+
constructor(timeoutsCache, logger, shallSkip = false) {
|
|
7
|
+
this.timeoutsCache = timeoutsCache;
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
this.shallSkip = shallSkip;
|
|
10
|
+
}
|
|
11
|
+
getLocalTimeouts(callId) {
|
|
12
|
+
if (!this.activeLocalTimeouts.has(callId)) {
|
|
13
|
+
this.activeLocalTimeouts.set(callId, {});
|
|
14
|
+
}
|
|
15
|
+
return this.activeLocalTimeouts.get(callId);
|
|
16
|
+
}
|
|
17
|
+
async getActiveTimeouts(callId) {
|
|
18
|
+
const state = await this.timeoutsCache.getContext(callId, { timeouts: {} });
|
|
19
|
+
return state.timeouts;
|
|
20
|
+
}
|
|
21
|
+
async deleteTimeout(callId, key, timeoutId) {
|
|
22
|
+
const state = await this.timeoutsCache.getContext(callId);
|
|
23
|
+
if (state?.timeouts[key] && (!timeoutId || state.timeouts[key] === timeoutId)) {
|
|
24
|
+
delete state.timeouts[key];
|
|
25
|
+
await this.timeoutsCache.setContext(callId, state);
|
|
26
|
+
this.logger.debug(`TimeoutManager: removed timeout '${key}' from state`, {
|
|
27
|
+
callId,
|
|
28
|
+
key,
|
|
29
|
+
timeoutId,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const localTimeouts = this.getLocalTimeouts(callId);
|
|
33
|
+
if (localTimeouts[key] && (!timeoutId || localTimeouts[key].id === timeoutId)) {
|
|
34
|
+
clearTimeout(localTimeouts[key].timer);
|
|
35
|
+
delete localTimeouts[key];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async setTimeout(callId, key, cb, timeout) {
|
|
39
|
+
if (this.shallSkip) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const localTimeouts = this.getLocalTimeouts(callId);
|
|
43
|
+
if (localTimeouts[key]) {
|
|
44
|
+
clearTimeout(localTimeouts[key].timer);
|
|
45
|
+
}
|
|
46
|
+
const timeoutId = Date.now();
|
|
47
|
+
const state = await this.timeoutsCache.getContext(callId, { timeouts: {} });
|
|
48
|
+
state.timeouts[key] = timeoutId;
|
|
49
|
+
await this.timeoutsCache.setContext(callId, state);
|
|
50
|
+
const createdTimeout = setTimeout(() => {
|
|
51
|
+
void (async () => {
|
|
52
|
+
const currentState = await this.timeoutsCache.getContext(callId);
|
|
53
|
+
if (!currentState)
|
|
54
|
+
return;
|
|
55
|
+
if (currentState.timeouts[key] !== timeoutId) {
|
|
56
|
+
this.logger.debug('TimeoutManager: skipping timeout (cancelled or superseded)', {
|
|
57
|
+
callId,
|
|
58
|
+
key,
|
|
59
|
+
timeoutId,
|
|
60
|
+
currentTimeoutId: currentState.timeouts[key] || null,
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await cb();
|
|
66
|
+
this.logger.debug('TimeoutManager: executed timeout', {
|
|
67
|
+
callId,
|
|
68
|
+
timeoutId,
|
|
69
|
+
key,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.logger.error('TimeoutManager: error processing timeout', {
|
|
74
|
+
callId,
|
|
75
|
+
key,
|
|
76
|
+
error,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await this.deleteTimeout(callId, key, timeoutId);
|
|
81
|
+
}
|
|
82
|
+
})();
|
|
83
|
+
}, timeout);
|
|
84
|
+
localTimeouts[key] = { id: timeoutId, timer: createdTimeout };
|
|
85
|
+
this.logger.debug(`TimeoutManager: set timeout '${key}' in ${timeout} ms`, {
|
|
86
|
+
callId,
|
|
87
|
+
timeoutId,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async clearAllTimeouts(callId) {
|
|
91
|
+
const localTimeouts = this.getLocalTimeouts(callId);
|
|
92
|
+
for (const key in localTimeouts) {
|
|
93
|
+
clearTimeout(localTimeouts[key].timer);
|
|
94
|
+
}
|
|
95
|
+
this.activeLocalTimeouts.delete(callId);
|
|
96
|
+
await this.timeoutsCache.clearContext(callId);
|
|
97
|
+
this.logger.debug(`TimeoutManager: cleared all timeouts`, { callId });
|
|
98
|
+
}
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recombine-ai/platform",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Recombine AI: voice agent platform",
|
|
5
5
|
"license": "EULA",
|
|
6
6
|
"author": "recombine.ai",
|
|
@@ -13,14 +13,19 @@
|
|
|
13
13
|
"build"
|
|
14
14
|
],
|
|
15
15
|
"type": "module",
|
|
16
|
+
"types": "./build/index.d.ts",
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@recombine-ai/engine": "1.0.0",
|
|
18
|
-
"@recombine-ai/telescope": "0.1.
|
|
19
|
+
"@recombine-ai/telescope": "0.1.1",
|
|
19
20
|
"redis": "5.11.0",
|
|
20
21
|
"zod": "^4.4.3"
|
|
21
22
|
},
|
|
22
23
|
"exports": {
|
|
23
|
-
".":
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./build/index.d.ts",
|
|
26
|
+
"import": "./build/index.js",
|
|
27
|
+
"default": "./build/index.js"
|
|
28
|
+
}
|
|
24
29
|
},
|
|
25
30
|
"publishConfig": {
|
|
26
31
|
"registry": "https://registry.npmjs.org"
|