@recombine-ai/platform 0.2.6-test.7 → 0.2.6-test.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/build/analytics.d.ts +28 -18
- package/build/analytics.d.ts.map +1 -1
- package/build/analytics.js +80 -44
- package/build/interfaces/platform.d.ts +24 -5
- package/build/interfaces/platform.d.ts.map +1 -1
- package/build/interfaces/platform.js +17 -2
- package/build/postgres-analytics-query.d.ts +5 -5
- package/build/postgres-analytics-query.d.ts.map +1 -1
- package/build/postgres-analytics-query.js +17 -13
- package/build/postgres-analytics.d.ts +7 -7
- package/build/postgres-analytics.d.ts.map +1 -1
- package/build/postgres-analytics.js +41 -27
- package/package.json +1 -1
package/build/analytics.d.ts
CHANGED
|
@@ -1,30 +1,40 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export type
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
readonly key: string;
|
|
2
|
+
export type AnalyticsScalar = boolean | number | string;
|
|
3
|
+
export type AnalyticsFieldType = 'boolean' | 'number' | 'string';
|
|
4
|
+
export interface AnalyticsFieldDefinition<T> {
|
|
5
|
+
readonly type: AnalyticsFieldType;
|
|
7
6
|
readonly schema: z.ZodType<T>;
|
|
8
7
|
}
|
|
9
|
-
export type
|
|
8
|
+
export type AnalyticsFieldDefinitions = Readonly<Record<string, AnalyticsFieldDefinition<unknown>>>;
|
|
9
|
+
export type AnalyticsValue<TField extends AnalyticsFieldDefinition<unknown>> = TField extends AnalyticsFieldDefinition<infer TValue> ? TValue : never;
|
|
10
|
+
type AnalyticsFieldName<TFields extends AnalyticsFieldDefinitions> = Extract<keyof TFields, string>;
|
|
10
11
|
export interface ConversationAnalyticsSummary {
|
|
11
12
|
conversationId: string;
|
|
13
|
+
agentId: string;
|
|
12
14
|
channel: string;
|
|
13
15
|
startedAt: Date;
|
|
14
16
|
}
|
|
15
|
-
export interface
|
|
16
|
-
set
|
|
17
|
-
clear(conversationId: string,
|
|
18
|
-
get
|
|
17
|
+
export interface AnalyticsStore {
|
|
18
|
+
set(conversationId: string, agentId: string, fieldName: string, value: AnalyticsScalar): Promise<void>;
|
|
19
|
+
clear(conversationId: string, agentId: string, fieldName: string): Promise<void>;
|
|
20
|
+
get(conversationId: string, agentId: string, fieldName: string): Promise<unknown | undefined>;
|
|
21
|
+
getSummary(conversationId: string, agentId: string): Promise<ConversationAnalyticsSummary | undefined>;
|
|
22
|
+
}
|
|
23
|
+
export interface AgentAnalytics<TFields extends AnalyticsFieldDefinitions> {
|
|
24
|
+
set<TName extends AnalyticsFieldName<TFields>>(conversationId: string, fieldName: TName, value: AnalyticsValue<TFields[TName]>): Promise<void>;
|
|
25
|
+
clear<TName extends AnalyticsFieldName<TFields>>(conversationId: string, fieldName: TName): Promise<void>;
|
|
26
|
+
get<TName extends AnalyticsFieldName<TFields>>(conversationId: string, fieldName: TName): Promise<AnalyticsValue<TFields[TName]> | undefined>;
|
|
19
27
|
getSummary(conversationId: string): Promise<ConversationAnalyticsSummary | undefined>;
|
|
20
28
|
}
|
|
21
|
-
export declare const
|
|
22
|
-
boolean(
|
|
23
|
-
number(
|
|
24
|
-
string(
|
|
25
|
-
enum<const TValues extends readonly [string, ...string[]]>(
|
|
26
|
-
json<T extends AnalyticsJsonValue>(key: string, schema: z.ZodType<T>): AnalyticsKey<T>;
|
|
29
|
+
export declare const analyticsField: {
|
|
30
|
+
boolean(): AnalyticsFieldDefinition<boolean>;
|
|
31
|
+
number(): AnalyticsFieldDefinition<number>;
|
|
32
|
+
string(): AnalyticsFieldDefinition<string>;
|
|
33
|
+
enum<const TValues extends readonly [string, ...string[]]>(values: TValues): AnalyticsFieldDefinition<TValues[number]>;
|
|
27
34
|
};
|
|
28
|
-
export declare function
|
|
29
|
-
export declare function
|
|
35
|
+
export declare function validateAgentAnalytics(agentId: string, fields: AnalyticsFieldDefinitions): void;
|
|
36
|
+
export declare function createAgentAnalytics<TFields extends AnalyticsFieldDefinitions>(store: AnalyticsStore, agentId: string, fields: TFields): AgentAnalytics<TFields>;
|
|
37
|
+
export declare function createInMemoryAnalytics(summaries?: ConversationAnalyticsSummary[]): AnalyticsStore;
|
|
38
|
+
export declare function assertAnalyticsScalar(value: unknown): asserts value is AnalyticsScalar;
|
|
39
|
+
export {};
|
|
30
40
|
//# sourceMappingURL=analytics.d.ts.map
|
package/build/analytics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/build/analytics.js
CHANGED
|
@@ -1,78 +1,114 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.analyticsField = void 0;
|
|
4
|
+
exports.validateAgentAnalytics = validateAgentAnalytics;
|
|
5
|
+
exports.createAgentAnalytics = createAgentAnalytics;
|
|
4
6
|
exports.createInMemoryAnalytics = createInMemoryAnalytics;
|
|
5
|
-
exports.
|
|
7
|
+
exports.assertAnalyticsScalar = assertAnalyticsScalar;
|
|
6
8
|
const zod_1 = require("zod");
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
throw new Error('Analytics key contains unsupported characters');
|
|
10
|
-
}
|
|
11
|
-
return Object.freeze({ key, schema });
|
|
9
|
+
function defineField(type, schema) {
|
|
10
|
+
return Object.freeze({ type, schema });
|
|
12
11
|
}
|
|
13
|
-
exports.
|
|
14
|
-
boolean(
|
|
15
|
-
return
|
|
16
|
-
},
|
|
17
|
-
number(key) {
|
|
18
|
-
return defineKey(key, zod_1.z.number().finite());
|
|
12
|
+
exports.analyticsField = {
|
|
13
|
+
boolean() {
|
|
14
|
+
return defineField('boolean', zod_1.z.boolean());
|
|
19
15
|
},
|
|
20
|
-
|
|
21
|
-
return
|
|
16
|
+
number() {
|
|
17
|
+
return defineField('number', zod_1.z.number().finite());
|
|
22
18
|
},
|
|
23
|
-
|
|
24
|
-
return
|
|
19
|
+
string() {
|
|
20
|
+
return defineField('string', zod_1.z.string());
|
|
25
21
|
},
|
|
26
|
-
|
|
27
|
-
return
|
|
22
|
+
enum(values) {
|
|
23
|
+
return defineField('string', zod_1.z.enum(values));
|
|
28
24
|
},
|
|
29
25
|
};
|
|
26
|
+
function validateAgentAnalytics(agentId, fields) {
|
|
27
|
+
if (agentId.trim() === '') {
|
|
28
|
+
throw new Error('Analytics agent ID must not be empty');
|
|
29
|
+
}
|
|
30
|
+
for (const [fieldName, definition] of Object.entries(fields)) {
|
|
31
|
+
if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(fieldName)) {
|
|
32
|
+
throw new Error('Analytics field name contains unsupported characters');
|
|
33
|
+
}
|
|
34
|
+
if (!['boolean', 'number', 'string'].includes(definition.type)) {
|
|
35
|
+
throw new Error(`Analytics field '${fieldName}' has an unsupported type`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function createAgentAnalytics(store, agentId, fields) {
|
|
40
|
+
validateAgentAnalytics(agentId, fields);
|
|
41
|
+
const getDefinition = (fieldName) => {
|
|
42
|
+
const definition = fields[fieldName];
|
|
43
|
+
if (!definition) {
|
|
44
|
+
throw new Error(`Analytics field '${fieldName}' is not declared for agent '${agentId}'`);
|
|
45
|
+
}
|
|
46
|
+
return definition;
|
|
47
|
+
};
|
|
48
|
+
const parseValue = (fieldName, value) => getDefinition(fieldName).schema.parse(value);
|
|
49
|
+
return {
|
|
50
|
+
async set(conversationId, fieldName, value) {
|
|
51
|
+
const parsed = parseValue(fieldName, value);
|
|
52
|
+
assertAnalyticsScalar(parsed);
|
|
53
|
+
await store.set(conversationId, agentId, fieldName, parsed);
|
|
54
|
+
},
|
|
55
|
+
async clear(conversationId, fieldName) {
|
|
56
|
+
getDefinition(fieldName);
|
|
57
|
+
await store.clear(conversationId, agentId, fieldName);
|
|
58
|
+
},
|
|
59
|
+
async get(conversationId, fieldName) {
|
|
60
|
+
getDefinition(fieldName);
|
|
61
|
+
const value = await store.get(conversationId, agentId, fieldName);
|
|
62
|
+
return value === undefined ? undefined : parseValue(fieldName, value);
|
|
63
|
+
},
|
|
64
|
+
getSummary(conversationId) {
|
|
65
|
+
return store.getSummary(conversationId, agentId);
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
30
69
|
function createInMemoryAnalytics(summaries = []) {
|
|
31
70
|
const conversations = new Map();
|
|
71
|
+
const conversationAgents = new Map(summaries.map((summary) => [summary.conversationId, summary.agentId]));
|
|
32
72
|
const summariesById = new Map(summaries.map((summary) => [summary.conversationId, structuredClone(summary)]));
|
|
33
73
|
return {
|
|
34
|
-
async set(conversationId,
|
|
35
|
-
const
|
|
36
|
-
|
|
74
|
+
async set(conversationId, agentId, fieldName, value) {
|
|
75
|
+
const existingAgentId = conversationAgents.get(conversationId);
|
|
76
|
+
if (existingAgentId !== undefined && existingAgentId !== agentId) {
|
|
77
|
+
throw new Error(`Conversation '${conversationId}' belongs to another agent`);
|
|
78
|
+
}
|
|
79
|
+
assertAnalyticsScalar(value);
|
|
37
80
|
const values = conversations.get(conversationId) ?? new Map();
|
|
38
|
-
values.set(
|
|
81
|
+
values.set(fieldName, value);
|
|
39
82
|
conversations.set(conversationId, values);
|
|
83
|
+
conversationAgents.set(conversationId, agentId);
|
|
40
84
|
},
|
|
41
|
-
async clear(conversationId,
|
|
85
|
+
async clear(conversationId, agentId, fieldName) {
|
|
86
|
+
if (conversationAgents.get(conversationId) !== agentId) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
42
89
|
const values = conversations.get(conversationId);
|
|
43
|
-
values?.delete(
|
|
90
|
+
values?.delete(fieldName);
|
|
44
91
|
if (values?.size === 0) {
|
|
45
92
|
conversations.delete(conversationId);
|
|
46
93
|
}
|
|
47
94
|
},
|
|
48
|
-
async get(conversationId,
|
|
49
|
-
|
|
50
|
-
if (!values?.has(key.key)) {
|
|
95
|
+
async get(conversationId, agentId, fieldName) {
|
|
96
|
+
if (conversationAgents.get(conversationId) !== agentId) {
|
|
51
97
|
return undefined;
|
|
52
98
|
}
|
|
53
|
-
|
|
54
|
-
return key.schema.parse(structuredClone(value));
|
|
99
|
+
return conversations.get(conversationId)?.get(fieldName);
|
|
55
100
|
},
|
|
56
|
-
async getSummary(conversationId) {
|
|
101
|
+
async getSummary(conversationId, agentId) {
|
|
57
102
|
const summary = summariesById.get(conversationId);
|
|
58
|
-
return summary ? structuredClone(summary) : undefined;
|
|
103
|
+
return summary?.agentId === agentId ? structuredClone(summary) : undefined;
|
|
59
104
|
},
|
|
60
105
|
};
|
|
61
106
|
}
|
|
62
|
-
function
|
|
63
|
-
if (value ===
|
|
64
|
-
typeof value === 'boolean' ||
|
|
107
|
+
function assertAnalyticsScalar(value) {
|
|
108
|
+
if (typeof value === 'boolean' ||
|
|
65
109
|
typeof value === 'string' ||
|
|
66
110
|
(typeof value === 'number' && Number.isFinite(value))) {
|
|
67
111
|
return;
|
|
68
112
|
}
|
|
69
|
-
|
|
70
|
-
value.forEach(assertAnalyticsJsonValue);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
74
|
-
Object.values(value).forEach(assertAnalyticsJsonValue);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
throw new Error('Analytics value must be valid JSON');
|
|
113
|
+
throw new Error('Analytics value must be a boolean, finite number, or string');
|
|
78
114
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ReadableStream } from 'node:stream/web';
|
|
2
2
|
import { AIEngine, AIStreamEngine, LlmAdapter, LlmStreamAdapter, Logger, Message, PromptFS, ResponseChunk } from '@recombine-ai/engine';
|
|
3
|
-
import {
|
|
3
|
+
import { AgentAnalytics, AnalyticsFieldDefinitions, AnalyticsStore } from '../analytics';
|
|
4
4
|
import { ContextCache } from '../context-cache';
|
|
5
5
|
import { TimeoutManager } from '../timeouts-manager';
|
|
6
6
|
import { IRcVoice } from './rc-voice';
|
|
@@ -18,24 +18,43 @@ export interface Platform<TContext extends {}, TModels extends IModels, TRcVoice
|
|
|
18
18
|
models: TModels['models'];
|
|
19
19
|
streamingModels: TModels['streamingModels'];
|
|
20
20
|
contextCache: TContextCache;
|
|
21
|
-
analytics:
|
|
21
|
+
analytics: AnalyticsStore;
|
|
22
22
|
}
|
|
23
23
|
export interface CallInfo {
|
|
24
24
|
callId: string;
|
|
25
25
|
phoneNumber?: string;
|
|
26
26
|
headers: Record<string, unknown>;
|
|
27
27
|
}
|
|
28
|
+
export type VoiceSettings = Record<string, string | number | boolean | number[]>;
|
|
28
29
|
export interface VoiceAgent {
|
|
29
30
|
onCallStart: (call: CallInfo) => Promise<{
|
|
30
31
|
initialMessage: string;
|
|
31
|
-
initialSettings?:
|
|
32
|
+
initialSettings?: VoiceSettings;
|
|
32
33
|
}>;
|
|
33
34
|
onCallReady: (call: CallInfo) => Promise<void>;
|
|
34
35
|
streamResponse: (messages: Message[], call: CallInfo) => Promise<ReadableStream<ResponseChunk>>;
|
|
35
|
-
onCallEnd: (call: CallInfo) => Promise<void>;
|
|
36
|
+
onCallEnd: (call: CallInfo, completedConversation?: Message[]) => Promise<void>;
|
|
36
37
|
onAgentSpeechDone: (messages: Message[], call: CallInfo) => Promise<void>;
|
|
37
38
|
onServiceStartup: () => Promise<void>;
|
|
38
39
|
onFatalError: (callId: string, err: any) => Promise<void>;
|
|
39
40
|
}
|
|
40
|
-
export
|
|
41
|
+
export type AgentPlatform<TPlatform extends {
|
|
42
|
+
analytics: AnalyticsStore;
|
|
43
|
+
}, TFields extends AnalyticsFieldDefinitions> = Omit<TPlatform, 'analytics'> & {
|
|
44
|
+
analytics: AgentAnalytics<TFields>;
|
|
45
|
+
};
|
|
46
|
+
type ComposedPlatform<TContext extends object, TModels extends IModels> = Platform<TContext, TModels, unknown, unknown, unknown>;
|
|
47
|
+
export interface AgentDefinition<TFields extends AnalyticsFieldDefinitions> {
|
|
48
|
+
readonly agentId: string;
|
|
49
|
+
readonly analytics: Readonly<TFields>;
|
|
50
|
+
}
|
|
51
|
+
export interface AgentDeclaration<TContext extends object, TServices, TModels extends IModels, TFields extends AnalyticsFieldDefinitions, TPlatform extends ComposedPlatform<TContext, TModels> = Platform<TContext, TModels>> extends AgentDefinition<TFields> {
|
|
52
|
+
create: (platform: AgentPlatform<TPlatform, TFields>, services: TServices) => VoiceAgent;
|
|
53
|
+
}
|
|
54
|
+
export interface AgentCreator<TContext extends object, TServices, TModels extends IModels, TFields extends AnalyticsFieldDefinitions, TPlatform extends ComposedPlatform<TContext, TModels> = Platform<TContext, TModels>> {
|
|
55
|
+
(platform: TPlatform, services: TServices): VoiceAgent;
|
|
56
|
+
readonly definition: AgentDefinition<TFields>;
|
|
57
|
+
}
|
|
58
|
+
export declare function createAgent<TContext extends object, TServices, TModels extends IModels, const TFields extends AnalyticsFieldDefinitions, TPlatform extends ComposedPlatform<TContext, TModels> = Platform<TContext, TModels>>(declaration: AgentDeclaration<TContext, TServices, TModels, TFields, TPlatform>): AgentCreator<TContext, TServices, TModels, TFields, TPlatform>;
|
|
59
|
+
export {};
|
|
41
60
|
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/interfaces/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAgent = createAgent;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const analytics_1 = require("../analytics");
|
|
5
|
+
function createAgent(declaration) {
|
|
6
|
+
const { agentId, create } = declaration;
|
|
7
|
+
const analytics = Object.freeze({ ...declaration.analytics });
|
|
8
|
+
(0, analytics_1.validateAgentAnalytics)(agentId, analytics);
|
|
9
|
+
const creator = (platform, services) => {
|
|
10
|
+
const agentPlatform = {
|
|
11
|
+
...platform,
|
|
12
|
+
analytics: (0, analytics_1.createAgentAnalytics)(platform.analytics, agentId, analytics),
|
|
13
|
+
};
|
|
14
|
+
return create(agentPlatform, services);
|
|
15
|
+
};
|
|
16
|
+
const definition = Object.freeze({ agentId, analytics });
|
|
17
|
+
return Object.defineProperty(creator, 'definition', {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
value: definition,
|
|
20
|
+
});
|
|
6
21
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import {
|
|
2
|
+
import { AnalyticsScalar } from './analytics';
|
|
3
3
|
export type AnalyticsFilterScalar = boolean | number | string;
|
|
4
4
|
export type AnalyticsFilterValueType = 'boolean' | 'number' | 'string';
|
|
5
5
|
export type AnalyticsFilterOperator = 'eq' | 'exists' | 'gt' | 'gte' | 'in' | 'lt' | 'lte';
|
|
@@ -24,14 +24,14 @@ export type AnalyticsQueryParameters = Readonly<Record<string, unknown>>;
|
|
|
24
24
|
export interface TelescopeConversation {
|
|
25
25
|
conversationId: string;
|
|
26
26
|
medium: string;
|
|
27
|
-
agentId
|
|
27
|
+
agentId: string;
|
|
28
28
|
region?: string;
|
|
29
29
|
phoneNumber?: string;
|
|
30
30
|
duration?: number;
|
|
31
31
|
audioUrl?: string;
|
|
32
32
|
createdAt: number;
|
|
33
33
|
endedAt?: number;
|
|
34
|
-
analytics: Record<string,
|
|
34
|
+
analytics: Record<string, AnalyticsScalar>;
|
|
35
35
|
}
|
|
36
36
|
export interface TelescopeConversationPage {
|
|
37
37
|
limit: number;
|
|
@@ -42,14 +42,14 @@ export interface AnalyticsFieldValue {
|
|
|
42
42
|
value: AnalyticsFilterScalar;
|
|
43
43
|
count: number;
|
|
44
44
|
}
|
|
45
|
-
export interface
|
|
45
|
+
export interface AnalyticsFieldMetadata {
|
|
46
46
|
key: string;
|
|
47
47
|
type: AnalyticsFilterValueType;
|
|
48
48
|
variantCount: number;
|
|
49
49
|
values: AnalyticsFieldValue[];
|
|
50
50
|
}
|
|
51
51
|
export interface AnalyticsFields {
|
|
52
|
-
items:
|
|
52
|
+
items: AnalyticsFieldMetadata[];
|
|
53
53
|
}
|
|
54
54
|
export interface AnalyticsTimeSeriesItem {
|
|
55
55
|
startAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAElD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAI7C,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACtE,MAAM,MAAM,uBAAuB,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1F,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,wBAAwB,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,KAAK,CAAC,EAAE,qBAAqB,CAAA;IAC7B,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,eAAe,GACrB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,SAAS,EAAE,SAAS,GAAG,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnD,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;AACxE,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAIxE,MAAM,WAAW,qBAAqB;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,yBAAyB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,qBAAqB,EAAE,CAAA;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAID,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,qBAAqB,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,sBAAsB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,wBAAwB,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,sBAAsB,EAAE,CAAA;CAClC;AAID,MAAM,WAAW,uBAAuB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,eAAe,CAAA;IACvB,WAAW,EAAE,wBAAwB,CAAA;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,uBAAuB,EAAE,CAAA;CACnC;AAED,UAAU,SAAS;IACf,KAAK,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CACnC;AA6CD,qBAAa,mBAAoB,SAAQ,KAAK;IACjC,IAAI,SAAwB;CACxC;AAQD,qBAAa,6BAA6B;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAI1C,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAgBnF,gBAAgB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IA2CrF,kBAAkB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IA0C7E,aAAa,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAiErF;AAID,MAAM,WAAW,8BAA8B;IAC3C,OAAO,EAAE,6BAA6B,CAAA;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,wBAAgB,oCAAoC,CAChD,MAAM,EAAE,UAAU,GACnB,8BAA8B,CAMhC"}
|
|
@@ -20,7 +20,7 @@ class PostgresAnalyticsQueryService {
|
|
|
20
20
|
LEFT JOIN phone_calls p ON p.conversation_id = c.conversation_id
|
|
21
21
|
WHERE c.conversation_id = $1`, [conversationId]);
|
|
22
22
|
const row = result.rows[0];
|
|
23
|
-
return row ? conversationRowToJson(row) : undefined;
|
|
23
|
+
return row?.agent_id ? conversationRowToJson(row) : undefined;
|
|
24
24
|
}
|
|
25
25
|
async getConversations(query) {
|
|
26
26
|
const parsedLimit = Number.parseInt(optionalString(query.limit, 'limit') ?? '10', 10);
|
|
@@ -51,13 +51,10 @@ class PostgresAnalyticsQueryService {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
async getAnalyticsFields(query) {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
values.push(agentId);
|
|
59
|
-
where += ` AND c.agent_id = $${values.length}`;
|
|
60
|
-
}
|
|
54
|
+
const agentId = requiredString(query.agentId, 'agentId');
|
|
55
|
+
const values = [agentId];
|
|
56
|
+
const where = "WHERE jsonb_typeof(entry.value) IN ('boolean', 'number', 'string')" +
|
|
57
|
+
' AND c.agent_id = $1';
|
|
61
58
|
const result = await this.database.query(`WITH value_counts AS (
|
|
62
59
|
SELECT entry.key,
|
|
63
60
|
jsonb_typeof(entry.value) AS type,
|
|
@@ -297,16 +294,24 @@ function conversationRowToJson(row) {
|
|
|
297
294
|
return {
|
|
298
295
|
conversationId: row.conversation_id,
|
|
299
296
|
medium: row.channel,
|
|
300
|
-
agentId: row.agent_id
|
|
297
|
+
agentId: row.agent_id,
|
|
301
298
|
region: row.region || undefined,
|
|
302
299
|
phoneNumber: row.phone_number || undefined,
|
|
303
300
|
duration: row.duration_seconds ?? undefined,
|
|
304
301
|
audioUrl: row.recording_url || undefined,
|
|
305
302
|
createdAt: new Date(row.started_at).getTime(),
|
|
306
303
|
endedAt: row.ended_at ? new Date(row.ended_at).getTime() : undefined,
|
|
307
|
-
analytics: row.analytics
|
|
304
|
+
analytics: analyticsScalars(row.analytics),
|
|
308
305
|
};
|
|
309
306
|
}
|
|
307
|
+
function analyticsScalars(values) {
|
|
308
|
+
return Object.fromEntries(Object.entries(values ?? {}).filter((entry) => {
|
|
309
|
+
const value = entry[1];
|
|
310
|
+
return (typeof value === 'boolean' ||
|
|
311
|
+
typeof value === 'string' ||
|
|
312
|
+
(typeof value === 'number' && Number.isFinite(value)));
|
|
313
|
+
}));
|
|
314
|
+
}
|
|
310
315
|
function analyticsFieldRowsToJson(rows) {
|
|
311
316
|
const fields = new Map();
|
|
312
317
|
for (const row of rows) {
|
|
@@ -339,12 +344,11 @@ function buildConversationConditions(query) {
|
|
|
339
344
|
if (from && to && from >= to) {
|
|
340
345
|
throw new AnalyticsQueryError('from must be earlier than to');
|
|
341
346
|
}
|
|
342
|
-
const agentId =
|
|
347
|
+
const agentId = requiredString(query.agentId, 'agentId');
|
|
343
348
|
const region = optionalString(query.region, 'region');
|
|
344
349
|
const channel = optionalString(query.channel, 'channel');
|
|
345
350
|
const phoneNumber = optionalString(query.phoneNumber, 'phoneNumber');
|
|
346
|
-
|
|
347
|
-
conditions.push(`c.agent_id = ${bind(agentId)}`);
|
|
351
|
+
conditions.push(`c.agent_id = ${bind(agentId)}`);
|
|
348
352
|
if (region)
|
|
349
353
|
conditions.push(`c.region = ${bind(region)}`);
|
|
350
354
|
if (channel)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Pool, PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import {
|
|
2
|
+
import { AnalyticsScalar, AnalyticsStore, ConversationAnalyticsSummary } from './analytics';
|
|
3
3
|
import { PostgresAnalyticsQueryService } from './postgres-analytics-query';
|
|
4
4
|
export interface ConversationRecord {
|
|
5
5
|
conversationId: string;
|
|
6
|
-
agentId
|
|
6
|
+
agentId: string;
|
|
7
7
|
channel: string;
|
|
8
8
|
region?: string;
|
|
9
9
|
startedAt: Date;
|
|
@@ -18,13 +18,13 @@ export interface PhoneCallRecord {
|
|
|
18
18
|
interface Queryable {
|
|
19
19
|
query<TResult extends Record<string, unknown> = Record<string, unknown>>(text: string, values?: unknown[]): Promise<QueryResult<TResult>>;
|
|
20
20
|
}
|
|
21
|
-
export declare class PostgresAnalytics implements
|
|
21
|
+
export declare class PostgresAnalytics implements AnalyticsStore {
|
|
22
22
|
private readonly database;
|
|
23
23
|
constructor(database: Queryable);
|
|
24
|
-
set
|
|
25
|
-
clear(conversationId: string,
|
|
26
|
-
get
|
|
27
|
-
getSummary(conversationId: string): Promise<ConversationAnalyticsSummary | undefined>;
|
|
24
|
+
set(conversationId: string, agentId: string, fieldName: string, value: AnalyticsScalar): Promise<void>;
|
|
25
|
+
clear(conversationId: string, agentId: string, fieldName: string): Promise<void>;
|
|
26
|
+
get(conversationId: string, agentId: string, fieldName: string): Promise<unknown | undefined>;
|
|
27
|
+
getSummary(conversationId: string, agentId: string): Promise<ConversationAnalyticsSummary | undefined>;
|
|
28
28
|
}
|
|
29
29
|
export declare class PostgresConversationRepository {
|
|
30
30
|
private readonly pool;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAE9D,OAAO,EACH,
|
|
1
|
+
{"version":3,"file":"postgres-analytics.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAE9D,OAAO,EACH,eAAe,EACf,cAAc,EACd,4BAA4B,EAE/B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAI1E,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID,MAAM,WAAW,eAAe;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAID,UAAU,SAAS;IACf,KAAK,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CACnC;AAaD,qBAAa,iBAAkB,YAAW,cAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAE1C,GAAG,CACL,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC;IAeV,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAchF,GAAG,CACL,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAezB,UAAU,CACZ,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,GAChB,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC;CAqBvD;AAgBD,qBAAa,8BAA8B;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,gBAAgB,CAClB,YAAY,EAAE,kBAAkB,EAChC,SAAS,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,IAAI,CAAC;YAmBF,kBAAkB;YA4ClB,eAAe;CA0BhC;AAsED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDxE;AA6BD,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,8BAA8B,CAAA;IAC7C,OAAO,EAAE,6BAA6B,CAAA;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAID,wBAAsB,+BAA+B,CACjD,MAAM,EAAE,UAAU,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAgBpC"}
|
|
@@ -11,55 +11,56 @@ class PostgresAnalytics {
|
|
|
11
11
|
constructor(database) {
|
|
12
12
|
this.database = database;
|
|
13
13
|
}
|
|
14
|
-
async set(conversationId,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const serialized = JSON.stringify(parsed);
|
|
18
|
-
if (serialized === undefined) {
|
|
19
|
-
throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
|
|
20
|
-
}
|
|
14
|
+
async set(conversationId, agentId, fieldName, value) {
|
|
15
|
+
(0, analytics_1.assertAnalyticsScalar)(value);
|
|
16
|
+
const serialized = JSON.stringify(value);
|
|
21
17
|
const result = await this.database.query(`UPDATE conversations
|
|
22
|
-
SET analytics = jsonb_set(analytics, ARRAY[$
|
|
18
|
+
SET analytics = jsonb_set(analytics, ARRAY[$3::text], $4::jsonb, true),
|
|
23
19
|
updated_at = now()
|
|
24
|
-
WHERE conversation_id = $1
|
|
25
|
-
|
|
20
|
+
WHERE conversation_id = $1
|
|
21
|
+
AND agent_id = $2`, [conversationId, agentId, fieldName, serialized]);
|
|
22
|
+
assertConversationBelongsToAgent(result.rowCount, conversationId, agentId);
|
|
26
23
|
}
|
|
27
|
-
async clear(conversationId,
|
|
24
|
+
async clear(conversationId, agentId, fieldName) {
|
|
28
25
|
const result = await this.database.query(`UPDATE conversations
|
|
29
|
-
SET analytics = analytics - $
|
|
26
|
+
SET analytics = analytics - $3::text,
|
|
30
27
|
updated_at = now()
|
|
31
|
-
WHERE conversation_id = $1
|
|
32
|
-
|
|
28
|
+
WHERE conversation_id = $1
|
|
29
|
+
AND agent_id = $2`, [conversationId, agentId, fieldName]);
|
|
30
|
+
assertConversationBelongsToAgent(result.rowCount, conversationId, agentId);
|
|
33
31
|
}
|
|
34
|
-
async get(conversationId,
|
|
35
|
-
const result = await this.database.query(`SELECT analytics ? $
|
|
36
|
-
analytics -> $
|
|
32
|
+
async get(conversationId, agentId, fieldName) {
|
|
33
|
+
const result = await this.database.query(`SELECT analytics ? $3::text AS present,
|
|
34
|
+
analytics -> $3::text AS value
|
|
37
35
|
FROM conversations
|
|
38
|
-
WHERE conversation_id = $1
|
|
36
|
+
WHERE conversation_id = $1
|
|
37
|
+
AND agent_id = $2`, [conversationId, agentId, fieldName]);
|
|
39
38
|
if (result.rows.length === 0 || !result.rows[0].present) {
|
|
40
39
|
return undefined;
|
|
41
40
|
}
|
|
42
|
-
return
|
|
41
|
+
return result.rows[0].value;
|
|
43
42
|
}
|
|
44
|
-
async getSummary(conversationId) {
|
|
45
|
-
const result = await this.database.query(`SELECT conversation_id, channel, started_at
|
|
43
|
+
async getSummary(conversationId, agentId) {
|
|
44
|
+
const result = await this.database.query(`SELECT conversation_id, agent_id, channel, started_at
|
|
46
45
|
FROM conversations
|
|
47
|
-
WHERE conversation_id = $1
|
|
46
|
+
WHERE conversation_id = $1
|
|
47
|
+
AND agent_id = $2`, [conversationId, agentId]);
|
|
48
48
|
const row = result.rows[0];
|
|
49
49
|
if (!row) {
|
|
50
50
|
return undefined;
|
|
51
51
|
}
|
|
52
52
|
return {
|
|
53
53
|
conversationId: row.conversation_id,
|
|
54
|
+
agentId: row.agent_id,
|
|
54
55
|
channel: row.channel,
|
|
55
56
|
startedAt: row.started_at,
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
exports.PostgresAnalytics = PostgresAnalytics;
|
|
60
|
-
function
|
|
61
|
+
function assertConversationBelongsToAgent(rowCount, conversationId, agentId) {
|
|
61
62
|
if (rowCount !== 1) {
|
|
62
|
-
throw new Error(`Conversation '${conversationId}' does not
|
|
63
|
+
throw new Error(`Conversation '${conversationId}' does not belong to agent '${agentId}'`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
class PostgresConversationRepository {
|
|
@@ -86,7 +87,7 @@ class PostgresConversationRepository {
|
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
async upsertConversation(client, conversation) {
|
|
89
|
-
await client.query(`INSERT INTO conversations (
|
|
90
|
+
const result = await client.query(`INSERT INTO conversations (
|
|
90
91
|
conversation_id,
|
|
91
92
|
agent_id,
|
|
92
93
|
channel,
|
|
@@ -103,15 +104,18 @@ class PostgresConversationRepository {
|
|
|
103
104
|
conversations.duration_seconds,
|
|
104
105
|
EXCLUDED.duration_seconds
|
|
105
106
|
),
|
|
106
|
-
updated_at = now()
|
|
107
|
+
updated_at = now()
|
|
108
|
+
WHERE conversations.agent_id IS NULL
|
|
109
|
+
OR conversations.agent_id = EXCLUDED.agent_id`, [
|
|
107
110
|
conversation.conversationId,
|
|
108
|
-
conversation.agentId
|
|
111
|
+
conversation.agentId,
|
|
109
112
|
conversation.channel,
|
|
110
113
|
conversation.region ?? null,
|
|
111
114
|
conversation.startedAt,
|
|
112
115
|
conversation.endedAt ?? null,
|
|
113
116
|
conversation.durationSeconds ?? null,
|
|
114
117
|
]);
|
|
118
|
+
assertConversationBelongsToAgent(result.rowCount, conversation.conversationId, conversation.agentId);
|
|
115
119
|
}
|
|
116
120
|
async upsertPhoneCall(client, conversationId, phoneCall) {
|
|
117
121
|
await client.query(`INSERT INTO phone_calls (
|
|
@@ -184,6 +188,16 @@ const migrations = [
|
|
|
184
188
|
ON phone_calls (phone_number, conversation_id);
|
|
185
189
|
`,
|
|
186
190
|
},
|
|
191
|
+
{
|
|
192
|
+
version: 2,
|
|
193
|
+
sql: `
|
|
194
|
+
-- Analytics rationale: Enforce mandatory agent ownership for new and updated rows
|
|
195
|
+
-- without making deployment fail on historical conversations that have no agent ID.
|
|
196
|
+
ALTER TABLE conversations
|
|
197
|
+
ADD CONSTRAINT conversations_agent_id_required
|
|
198
|
+
CHECK (agent_id IS NOT NULL AND btrim(agent_id) <> '') NOT VALID;
|
|
199
|
+
`,
|
|
200
|
+
},
|
|
187
201
|
];
|
|
188
202
|
async function migrateAnalyticsDatabase(pool) {
|
|
189
203
|
const client = await pool.connect();
|