@recombine-ai/platform 0.2.6-test.4 → 0.2.6-test.5
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.js +13 -8
- package/build/context-cache.js +6 -2
- package/build/index.d.ts +7 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23 -7
- package/build/interfaces/platform.d.ts +4 -4
- package/build/interfaces/platform.d.ts.map +1 -1
- package/build/interfaces/platform.js +4 -1
- package/build/interfaces/rc-voice.js +2 -1
- package/build/postgres-analytics-query.d.ts +1 -1
- package/build/postgres-analytics-query.d.ts.map +1 -1
- package/build/postgres-analytics-query.js +11 -5
- package/build/postgres-analytics.d.ts +2 -2
- package/build/postgres-analytics.d.ts.map +1 -1
- package/build/postgres-analytics.js +17 -10
- package/build/timeouts-manager.d.ts +1 -1
- package/build/timeouts-manager.d.ts.map +1 -1
- package/build/timeouts-manager.js +5 -1
- package/package.json +3 -10
- package/build/analytics.spec.d.ts +0 -2
- package/build/analytics.spec.d.ts.map +0 -1
- package/build/analytics.spec.js +0 -57
- package/build/postgres-analytics-query.spec.d.ts +0 -2
- package/build/postgres-analytics-query.spec.d.ts.map +0 -1
- package/build/postgres-analytics-query.spec.js +0 -223
- package/build/postgres-analytics.spec.d.ts +0 -2
- package/build/postgres-analytics.spec.d.ts.map +0 -1
- package/build/postgres-analytics.spec.js +0 -61
package/build/analytics.js
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyticsKey = void 0;
|
|
4
|
+
exports.createInMemoryAnalytics = createInMemoryAnalytics;
|
|
5
|
+
exports.assertAnalyticsJsonValue = assertAnalyticsJsonValue;
|
|
6
|
+
const zod_1 = require("zod");
|
|
2
7
|
function defineKey(key, schema) {
|
|
3
8
|
if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(key)) {
|
|
4
9
|
throw new Error('Analytics key contains unsupported characters');
|
|
5
10
|
}
|
|
6
11
|
return Object.freeze({ key, schema });
|
|
7
12
|
}
|
|
8
|
-
|
|
13
|
+
exports.analyticsKey = {
|
|
9
14
|
boolean(key) {
|
|
10
|
-
return defineKey(key, z.boolean());
|
|
15
|
+
return defineKey(key, zod_1.z.boolean());
|
|
11
16
|
},
|
|
12
17
|
number(key) {
|
|
13
|
-
return defineKey(key, z.number().finite());
|
|
18
|
+
return defineKey(key, zod_1.z.number().finite());
|
|
14
19
|
},
|
|
15
20
|
string(key) {
|
|
16
|
-
return defineKey(key, z.string());
|
|
21
|
+
return defineKey(key, zod_1.z.string());
|
|
17
22
|
},
|
|
18
23
|
enum(key, values) {
|
|
19
|
-
return defineKey(key, z.enum(values));
|
|
24
|
+
return defineKey(key, zod_1.z.enum(values));
|
|
20
25
|
},
|
|
21
26
|
json(key, schema) {
|
|
22
27
|
return defineKey(key, schema);
|
|
23
28
|
},
|
|
24
29
|
};
|
|
25
|
-
|
|
30
|
+
function createInMemoryAnalytics(summaries = []) {
|
|
26
31
|
const conversations = new Map();
|
|
27
32
|
const summariesById = new Map(summaries.map((summary) => [summary.conversationId, structuredClone(summary)]));
|
|
28
33
|
return {
|
|
@@ -58,7 +63,7 @@ export function createInMemoryAnalytics(summaries = []) {
|
|
|
58
63
|
},
|
|
59
64
|
};
|
|
60
65
|
}
|
|
61
|
-
|
|
66
|
+
function assertAnalyticsJsonValue(value) {
|
|
62
67
|
if (value === null ||
|
|
63
68
|
typeof value === 'boolean' ||
|
|
64
69
|
typeof value === 'string' ||
|
package/build/context-cache.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createInMemoryCache = createInMemoryCache;
|
|
4
|
+
exports.createRedisCache = createRedisCache;
|
|
5
|
+
function createInMemoryCache() {
|
|
2
6
|
const cache = new Map();
|
|
3
7
|
async function getContext(callId, defaultContext) {
|
|
4
8
|
if (cache.has(callId)) {
|
|
@@ -21,7 +25,7 @@ export function createInMemoryCache() {
|
|
|
21
25
|
},
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
|
-
|
|
28
|
+
function createRedisCache(client, options = DEFAULT_REDIS_CACHE_OPTIONS) {
|
|
25
29
|
function createKey(callId) {
|
|
26
30
|
return options.keyPrefix + callId;
|
|
27
31
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from './interfaces/platform
|
|
2
|
-
export * from './interfaces/rc-voice
|
|
3
|
-
export * from './context-cache
|
|
4
|
-
export * from './analytics
|
|
5
|
-
export * from './postgres-analytics
|
|
6
|
-
export * from './postgres-analytics-query
|
|
7
|
-
export * from './timeouts-manager
|
|
1
|
+
export * from './interfaces/platform';
|
|
2
|
+
export * from './interfaces/rc-voice';
|
|
3
|
+
export * from './context-cache';
|
|
4
|
+
export * from './analytics';
|
|
5
|
+
export * from './postgres-analytics';
|
|
6
|
+
export * from './postgres-analytics-query';
|
|
7
|
+
export * from './timeouts-manager';
|
|
8
8
|
//# 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":"
|
|
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;AAG/B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,oBAAoB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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("./interfaces/platform"), exports);
|
|
18
|
+
__exportStar(require("./interfaces/rc-voice"), exports);
|
|
19
|
+
__exportStar(require("./context-cache"), exports);
|
|
20
|
+
__exportStar(require("./analytics"), exports);
|
|
21
|
+
__exportStar(require("./postgres-analytics"), exports);
|
|
22
|
+
__exportStar(require("./postgres-analytics-query"), exports);
|
|
23
|
+
__exportStar(require("./timeouts-manager"), exports);
|
|
@@ -1,9 +1,9 @@
|
|
|
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 { Analytics } from '../analytics
|
|
4
|
-
import { ContextCache } from '../context-cache
|
|
5
|
-
import { TimeoutManager } from '../timeouts-manager
|
|
6
|
-
import { IRcVoice } from './rc-voice
|
|
3
|
+
import { Analytics } from '../analytics';
|
|
4
|
+
import { ContextCache } from '../context-cache';
|
|
5
|
+
import { TimeoutManager } from '../timeouts-manager';
|
|
6
|
+
import { IRcVoice } from './rc-voice';
|
|
7
7
|
export interface IModels {
|
|
8
8
|
models: Record<string, LlmAdapter>;
|
|
9
9
|
streamingModels: Record<string, LlmStreamAdapter>;
|
|
@@ -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;AAI7B,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
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;AAI7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,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;AAID,MAAM,WAAW,QAAQ,CACrB,QAAQ,SAAS,EAAE,EACnB,OAAO,SAAS,OAAO,EACvB,QAAQ,GAAG,QAAQ,EACnB,eAAe,GAAG,cAAc,EAChC,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC;IAGtC,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,eAAe,CAAA;IAE/B,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACzB,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAE3C,YAAY,EAAE,aAAa,CAAA;IAK3B,SAAS,EAAE,SAAS,CAAA;CACvB;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"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import { AnalyticsJsonValue } from './analytics
|
|
2
|
+
import { AnalyticsJsonValue } 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';
|
|
@@ -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,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresAnalyticsQueryService = exports.AnalyticsQueryError = void 0;
|
|
4
|
+
exports.createPostgresAnalyticsQueryServices = createPostgresAnalyticsQueryServices;
|
|
5
|
+
const pg_1 = require("pg");
|
|
6
|
+
class AnalyticsQueryError extends Error {
|
|
3
7
|
name = 'AnalyticsQueryError';
|
|
4
8
|
}
|
|
5
|
-
|
|
9
|
+
exports.AnalyticsQueryError = AnalyticsQueryError;
|
|
10
|
+
class PostgresAnalyticsQueryService {
|
|
6
11
|
database;
|
|
7
12
|
constructor(database) {
|
|
8
13
|
this.database = database;
|
|
@@ -139,8 +144,9 @@ export class PostgresAnalyticsQueryService {
|
|
|
139
144
|
};
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
exports.PostgresAnalyticsQueryService = PostgresAnalyticsQueryService;
|
|
148
|
+
function createPostgresAnalyticsQueryServices(config) {
|
|
149
|
+
const pool = new pg_1.Pool(config);
|
|
144
150
|
return {
|
|
145
151
|
queries: new PostgresAnalyticsQueryService(pool),
|
|
146
152
|
close: () => pool.end(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pool, PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import { Analytics, ConversationAnalytics } from './analytics
|
|
3
|
-
import { PostgresAnalyticsQueryService } from './postgres-analytics-query
|
|
2
|
+
import { Analytics, ConversationAnalytics } from './analytics';
|
|
3
|
+
import { PostgresAnalyticsQueryService } from './postgres-analytics-query';
|
|
4
4
|
export interface ConversationRecord {
|
|
5
5
|
conversationId: string;
|
|
6
6
|
agentId?: string;
|
|
@@ -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,SAAS,EAGT,qBAAqB,EAGxB,MAAM,
|
|
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,SAAS,EAGT,qBAAqB,EAGxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAI1E,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,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,cAAc,EAAE,MAAM,CAAA;IACtB,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;AAgBD,qBAAa,iBAAkB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAIhD,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB;CAqF1E;AAYD,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;YAoClB,eAAe;CAsBhC;AA4DD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CxE;AAID,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"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresConversationRepository = exports.PostgresAnalytics = void 0;
|
|
4
|
+
exports.migrateAnalyticsDatabase = migrateAnalyticsDatabase;
|
|
5
|
+
exports.createPostgresAnalyticsServices = createPostgresAnalyticsServices;
|
|
6
|
+
const pg_1 = require("pg");
|
|
7
|
+
const analytics_1 = require("./analytics");
|
|
8
|
+
const postgres_analytics_query_1 = require("./postgres-analytics-query");
|
|
9
|
+
class PostgresAnalytics {
|
|
5
10
|
database;
|
|
6
11
|
constructor(database) {
|
|
7
12
|
this.database = database;
|
|
@@ -11,7 +16,7 @@ export class PostgresAnalytics {
|
|
|
11
16
|
return {
|
|
12
17
|
async set(key, value) {
|
|
13
18
|
const parsed = key.schema.parse(value);
|
|
14
|
-
assertAnalyticsJsonValue(parsed);
|
|
19
|
+
(0, analytics_1.assertAnalyticsJsonValue)(parsed);
|
|
15
20
|
const serialized = JSON.stringify(parsed);
|
|
16
21
|
if (serialized === undefined) {
|
|
17
22
|
throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
|
|
@@ -66,12 +71,13 @@ export class PostgresAnalytics {
|
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
exports.PostgresAnalytics = PostgresAnalytics;
|
|
69
75
|
function assertConversationExists(rowCount, conversationId) {
|
|
70
76
|
if (rowCount !== 1) {
|
|
71
77
|
throw new Error(`Conversation '${conversationId}' does not exist`);
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
|
-
|
|
80
|
+
class PostgresConversationRepository {
|
|
75
81
|
pool;
|
|
76
82
|
constructor(pool) {
|
|
77
83
|
this.pool = pool;
|
|
@@ -139,6 +145,7 @@ export class PostgresConversationRepository {
|
|
|
139
145
|
]);
|
|
140
146
|
}
|
|
141
147
|
}
|
|
148
|
+
exports.PostgresConversationRepository = PostgresConversationRepository;
|
|
142
149
|
const migrations = [
|
|
143
150
|
{
|
|
144
151
|
version: 1,
|
|
@@ -192,7 +199,7 @@ const migrations = [
|
|
|
192
199
|
`,
|
|
193
200
|
},
|
|
194
201
|
];
|
|
195
|
-
|
|
202
|
+
async function migrateAnalyticsDatabase(pool) {
|
|
196
203
|
const client = await pool.connect();
|
|
197
204
|
try {
|
|
198
205
|
await client.query("SELECT pg_advisory_lock(hashtext('recombine_analytics_migrations'))");
|
|
@@ -226,8 +233,8 @@ export async function migrateAnalyticsDatabase(pool) {
|
|
|
226
233
|
client.release();
|
|
227
234
|
}
|
|
228
235
|
}
|
|
229
|
-
|
|
230
|
-
const pool = new Pool(config);
|
|
236
|
+
async function createPostgresAnalyticsServices(config) {
|
|
237
|
+
const pool = new pg_1.Pool(config);
|
|
231
238
|
try {
|
|
232
239
|
await migrateAnalyticsDatabase(pool);
|
|
233
240
|
}
|
|
@@ -238,7 +245,7 @@ export async function createPostgresAnalyticsServices(config) {
|
|
|
238
245
|
return {
|
|
239
246
|
analytics: new PostgresAnalytics(pool),
|
|
240
247
|
conversations: new PostgresConversationRepository(pool),
|
|
241
|
-
queries: new PostgresAnalyticsQueryService(pool),
|
|
248
|
+
queries: new postgres_analytics_query_1.PostgresAnalyticsQueryService(pool),
|
|
242
249
|
close: () => pool.end(),
|
|
243
250
|
};
|
|
244
251
|
}
|
|
@@ -1 +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;
|
|
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"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TimeoutManager = void 0;
|
|
4
|
+
class TimeoutManager {
|
|
2
5
|
timeoutsCache;
|
|
3
6
|
logger;
|
|
4
7
|
shallSkip;
|
|
@@ -97,3 +100,4 @@ export class TimeoutManager {
|
|
|
97
100
|
this.logger.debug(`TimeoutManager: cleared all timeouts`, { callId });
|
|
98
101
|
}
|
|
99
102
|
}
|
|
103
|
+
exports.TimeoutManager = TimeoutManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recombine-ai/platform",
|
|
3
|
-
"version": "0.2.6-test.
|
|
3
|
+
"version": "0.2.6-test.5",
|
|
4
4
|
"description": "Recombine AI: voice agent platform",
|
|
5
5
|
"license": "EULA",
|
|
6
6
|
"author": "recombine.ai",
|
|
@@ -12,23 +12,16 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"build"
|
|
14
14
|
],
|
|
15
|
-
"
|
|
15
|
+
"main": "./build/index.js",
|
|
16
16
|
"types": "./build/index.d.ts",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@recombine-ai/engine": "1.0.0",
|
|
19
|
-
"@recombine-ai/telescope": "0.
|
|
19
|
+
"@recombine-ai/telescope": "^0.3.0",
|
|
20
20
|
"@types/pg": "8.15.5",
|
|
21
21
|
"pg": "8.16.3",
|
|
22
22
|
"redis": "5.11.0",
|
|
23
23
|
"zod": "^4.4.3"
|
|
24
24
|
},
|
|
25
|
-
"exports": {
|
|
26
|
-
".": {
|
|
27
|
-
"types": "./build/index.d.ts",
|
|
28
|
-
"import": "./build/index.js",
|
|
29
|
-
"default": "./build/index.js"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
25
|
"publishConfig": {
|
|
33
26
|
"registry": "https://registry.npmjs.org"
|
|
34
27
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.spec.d.ts","sourceRoot":"","sources":["../src/analytics.spec.ts"],"names":[],"mappings":""}
|
package/build/analytics.spec.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { analyticsKey, createInMemoryAnalytics } from './analytics.js';
|
|
4
|
-
describe('Analytics', () => {
|
|
5
|
-
it('rejects unsupported key characters', () => {
|
|
6
|
-
expect(() => analyticsKey.string('not allowed')).toThrow('Analytics key contains unsupported characters');
|
|
7
|
-
});
|
|
8
|
-
it('keeps key and value types paired', async () => {
|
|
9
|
-
const analytics = createInMemoryAnalytics();
|
|
10
|
-
const escalated = analyticsKey.boolean('common.escalated');
|
|
11
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
12
|
-
await conversation.set(escalated, true);
|
|
13
|
-
const value = await conversation.get(escalated);
|
|
14
|
-
expectTypeOf(value).toEqualTypeOf();
|
|
15
|
-
expect(value).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
it('clears a value idempotently', async () => {
|
|
18
|
-
const analytics = createInMemoryAnalytics();
|
|
19
|
-
const score = analyticsKey.number('project.score');
|
|
20
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
21
|
-
await conversation.set(score, 3);
|
|
22
|
-
await conversation.clear(score);
|
|
23
|
-
await conversation.clear(score);
|
|
24
|
-
await expect(conversation.get(score)).resolves.toBeUndefined();
|
|
25
|
-
});
|
|
26
|
-
it('validates JSON values at runtime', async () => {
|
|
27
|
-
const analytics = createInMemoryAnalytics();
|
|
28
|
-
const result = analyticsKey.json('project.result', z.object({ reason: z.string(), accepted: z.boolean() }));
|
|
29
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
30
|
-
await expect(conversation.set(result, {
|
|
31
|
-
reason: 'done',
|
|
32
|
-
accepted: true,
|
|
33
|
-
})).resolves.toBeUndefined();
|
|
34
|
-
await expect(conversation.set(result, {
|
|
35
|
-
reason: 'done',
|
|
36
|
-
accepted: 'yes',
|
|
37
|
-
})).rejects.toThrow();
|
|
38
|
-
});
|
|
39
|
-
it('distinguishes JSON null from a missing key', async () => {
|
|
40
|
-
const analytics = createInMemoryAnalytics();
|
|
41
|
-
const result = analyticsKey.json('project.result', z.null());
|
|
42
|
-
await analytics.getConversationAnalytics('conversation-1').set(result, null);
|
|
43
|
-
await expect(analytics.getConversationAnalytics('conversation-1').get(result)).resolves.toBeNull();
|
|
44
|
-
await expect(analytics.getConversationAnalytics('conversation-2').get(result)).resolves.toBeUndefined();
|
|
45
|
-
});
|
|
46
|
-
it('returns common conversation data from getSummary', async () => {
|
|
47
|
-
const summary = {
|
|
48
|
-
conversationId: 'conversation-1',
|
|
49
|
-
agentId: 'support',
|
|
50
|
-
channel: 'phone',
|
|
51
|
-
startedAt: new Date('2026-08-22T10:00:00Z'),
|
|
52
|
-
durationSeconds: 60,
|
|
53
|
-
};
|
|
54
|
-
const analytics = createInMemoryAnalytics([summary]);
|
|
55
|
-
await expect(analytics.getConversationAnalytics('conversation-1').getSummary()).resolves.toEqual(summary);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics-query.spec.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { AnalyticsQueryError, PostgresAnalyticsQueryService } from './postgres-analytics-query.js';
|
|
3
|
-
function result(rows = []) {
|
|
4
|
-
return { rows, rowCount: rows.length };
|
|
5
|
-
}
|
|
6
|
-
describe('PostgresAnalyticsQueryService', () => {
|
|
7
|
-
it('binds each analytics filter once', async () => {
|
|
8
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
9
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
10
|
-
await service.getConversations({
|
|
11
|
-
agentId: 'collection-en',
|
|
12
|
-
limit: '20',
|
|
13
|
-
filters: JSON.stringify([
|
|
14
|
-
{
|
|
15
|
-
key: 'wandoo.disposition',
|
|
16
|
-
type: 'string',
|
|
17
|
-
operator: 'eq',
|
|
18
|
-
value: 'PAID',
|
|
19
|
-
},
|
|
20
|
-
]),
|
|
21
|
-
});
|
|
22
|
-
const [sql, values] = query.mock.calls[0];
|
|
23
|
-
expect(sql.match(/jsonb_build_object/g)).toHaveLength(1);
|
|
24
|
-
expect(values).toEqual(['collection-en', 'wandoo.disposition', '"PAID"', 21]);
|
|
25
|
-
});
|
|
26
|
-
it('ANDs a multiselect filter with the other analytics filters', async () => {
|
|
27
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
28
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
29
|
-
await service.getConversations({
|
|
30
|
-
filters: JSON.stringify([
|
|
31
|
-
{
|
|
32
|
-
key: 'wandoo.escalated',
|
|
33
|
-
type: 'boolean',
|
|
34
|
-
operator: 'eq',
|
|
35
|
-
value: true,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
key: 'wandoo.disposition',
|
|
39
|
-
type: 'string',
|
|
40
|
-
operator: 'in',
|
|
41
|
-
values: ['PAID', 'DROPPED'],
|
|
42
|
-
},
|
|
43
|
-
]),
|
|
44
|
-
});
|
|
45
|
-
const [sql, values] = query.mock.calls[0];
|
|
46
|
-
expect(sql).toContain('jsonb_build_object($1::text, $2::jsonb) AND');
|
|
47
|
-
expect(sql).toContain('(c.analytics @> jsonb_build_object($3::text, $4::jsonb) OR ' +
|
|
48
|
-
'c.analytics @> jsonb_build_object($3::text, $5::jsonb))');
|
|
49
|
-
expect(values).toEqual([
|
|
50
|
-
'wandoo.escalated',
|
|
51
|
-
'true',
|
|
52
|
-
'wandoo.disposition',
|
|
53
|
-
'"PAID"',
|
|
54
|
-
'"DROPPED"',
|
|
55
|
-
11,
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
it('uses the returned keyset cursor on the next page', async () => {
|
|
59
|
-
const firstStartedAt = new Date('2026-08-02T10:00:00Z');
|
|
60
|
-
const query = vi
|
|
61
|
-
.fn()
|
|
62
|
-
.mockResolvedValueOnce(result([
|
|
63
|
-
{
|
|
64
|
-
conversation_id: 'conversation-2',
|
|
65
|
-
channel: 'phone',
|
|
66
|
-
started_at: firstStartedAt,
|
|
67
|
-
analytics: {},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
conversation_id: 'conversation-1',
|
|
71
|
-
channel: 'phone',
|
|
72
|
-
started_at: new Date('2026-08-01T10:00:00Z'),
|
|
73
|
-
analytics: {},
|
|
74
|
-
},
|
|
75
|
-
]))
|
|
76
|
-
.mockResolvedValueOnce(result());
|
|
77
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
78
|
-
const firstPage = await service.getConversations({ limit: '1' });
|
|
79
|
-
expect(firstPage.lastConversationId).toBeTypeOf('string');
|
|
80
|
-
await service.getConversations({
|
|
81
|
-
limit: '1',
|
|
82
|
-
lastConversationId: firstPage.lastConversationId,
|
|
83
|
-
});
|
|
84
|
-
const [sql, values] = query.mock.calls[1];
|
|
85
|
-
expect(sql).toContain('(c.started_at, c.conversation_id) < ($1, $2)');
|
|
86
|
-
expect(values).toEqual([firstStartedAt, 'conversation-2', 2]);
|
|
87
|
-
});
|
|
88
|
-
it('rejects malformed filters without querying PostgreSQL', async () => {
|
|
89
|
-
const query = vi.fn();
|
|
90
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
91
|
-
await expect(service.getConversations({
|
|
92
|
-
filters: JSON.stringify([
|
|
93
|
-
{
|
|
94
|
-
key: 'wandoo.disposition',
|
|
95
|
-
type: 'string',
|
|
96
|
-
operator: 'gt',
|
|
97
|
-
value: 'PAID',
|
|
98
|
-
},
|
|
99
|
-
]),
|
|
100
|
-
})).rejects.toBeInstanceOf(AnalyticsQueryError);
|
|
101
|
-
expect(query).not.toHaveBeenCalled();
|
|
102
|
-
});
|
|
103
|
-
it('omits value counts for fields with more than 50 variants', async () => {
|
|
104
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
105
|
-
{
|
|
106
|
-
key: 'wandoo.disposition',
|
|
107
|
-
type: 'string',
|
|
108
|
-
value: 'PAID',
|
|
109
|
-
occurrence_count: '3',
|
|
110
|
-
variant_count: 2,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
key: 'wandoo.reference',
|
|
114
|
-
type: 'string',
|
|
115
|
-
value: 'first',
|
|
116
|
-
occurrence_count: '1',
|
|
117
|
-
variant_count: 51,
|
|
118
|
-
},
|
|
119
|
-
]));
|
|
120
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
121
|
-
await expect(service.getAnalyticsFields({ agentId: 'collection-en' })).resolves.toEqual({
|
|
122
|
-
items: [
|
|
123
|
-
{
|
|
124
|
-
key: 'wandoo.disposition',
|
|
125
|
-
type: 'string',
|
|
126
|
-
variantCount: 2,
|
|
127
|
-
values: [{ value: 'PAID', count: 3 }],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
key: 'wandoo.reference',
|
|
131
|
-
type: 'string',
|
|
132
|
-
variantCount: 51,
|
|
133
|
-
values: [],
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
it('returns a rate time series with numerator and denominator', async () => {
|
|
139
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
140
|
-
{
|
|
141
|
-
bucket_start: new Date('2026-08-03T00:00:00Z'),
|
|
142
|
-
metric_value: '0.25',
|
|
143
|
-
numerator: '1',
|
|
144
|
-
denominator: '4',
|
|
145
|
-
},
|
|
146
|
-
]));
|
|
147
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
148
|
-
const metric = {
|
|
149
|
-
operation: 'rate',
|
|
150
|
-
filter: {
|
|
151
|
-
key: 'wandoo.escalated',
|
|
152
|
-
type: 'boolean',
|
|
153
|
-
operator: 'eq',
|
|
154
|
-
value: true,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
await expect(service.getTimeSeries({
|
|
158
|
-
from: '2026-08-01T00:00:00Z',
|
|
159
|
-
to: '2026-09-01T00:00:00Z',
|
|
160
|
-
granularity: 'week',
|
|
161
|
-
metric: JSON.stringify(metric),
|
|
162
|
-
})).resolves.toEqual({
|
|
163
|
-
metric,
|
|
164
|
-
granularity: 'week',
|
|
165
|
-
from: '2026-08-01T00:00:00.000Z',
|
|
166
|
-
to: '2026-09-01T00:00:00.000Z',
|
|
167
|
-
items: [
|
|
168
|
-
{
|
|
169
|
-
startAt: '2026-08-03T00:00:00.000Z',
|
|
170
|
-
value: 0.25,
|
|
171
|
-
numerator: 1,
|
|
172
|
-
denominator: 4,
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
});
|
|
176
|
-
const [sql] = query.mock.calls[0];
|
|
177
|
-
expect(sql).toContain("date_trunc($3::text, c.started_at, 'UTC')");
|
|
178
|
-
});
|
|
179
|
-
it.each([
|
|
180
|
-
['average', 'AVG'],
|
|
181
|
-
['sum', 'SUM'],
|
|
182
|
-
])('builds the %s metric', async (operation, sqlFunction) => {
|
|
183
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
184
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
185
|
-
await service.getTimeSeries({
|
|
186
|
-
from: '2026-08-01T00:00:00Z',
|
|
187
|
-
to: '2026-09-01T00:00:00Z',
|
|
188
|
-
metric: JSON.stringify({ operation, key: 'wandoo.score' }),
|
|
189
|
-
});
|
|
190
|
-
const [sql, values] = query.mock.calls[0];
|
|
191
|
-
expect(sql).toContain(`${sqlFunction}(CASE WHEN jsonb_typeof`);
|
|
192
|
-
expect(values).toEqual([
|
|
193
|
-
new Date('2026-08-01T00:00:00Z'),
|
|
194
|
-
new Date('2026-09-01T00:00:00Z'),
|
|
195
|
-
'day',
|
|
196
|
-
'wandoo.score',
|
|
197
|
-
]);
|
|
198
|
-
});
|
|
199
|
-
it('maps one PostgreSQL conversation to the Telescope response', async () => {
|
|
200
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
201
|
-
{
|
|
202
|
-
conversation_id: 'conversation-1',
|
|
203
|
-
agent_id: 'collection-en',
|
|
204
|
-
channel: 'phone',
|
|
205
|
-
region: 'GB',
|
|
206
|
-
started_at: new Date('2026-08-01T10:00:00Z'),
|
|
207
|
-
ended_at: new Date('2026-08-01T10:02:00Z'),
|
|
208
|
-
duration_seconds: 120,
|
|
209
|
-
analytics: { 'wandoo.escalated': true },
|
|
210
|
-
phone_number: '+440000000000',
|
|
211
|
-
recording_url: 'https://example.test/recording.wav',
|
|
212
|
-
},
|
|
213
|
-
]));
|
|
214
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
215
|
-
await expect(service.getConversation('conversation-1')).resolves.toMatchObject({
|
|
216
|
-
conversationId: 'conversation-1',
|
|
217
|
-
medium: 'phone',
|
|
218
|
-
duration: 120,
|
|
219
|
-
analytics: { 'wandoo.escalated': true },
|
|
220
|
-
phoneNumber: '+440000000000',
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics.spec.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { analyticsKey } from './analytics.js';
|
|
4
|
-
import { PostgresAnalytics } from './postgres-analytics.js';
|
|
5
|
-
function result(rows = [], rowCount = rows.length) {
|
|
6
|
-
return { rows, rowCount };
|
|
7
|
-
}
|
|
8
|
-
describe('PostgresAnalytics', () => {
|
|
9
|
-
it('stores a typed value under one literal JSON key', async () => {
|
|
10
|
-
const query = vi.fn().mockResolvedValue(result([], 1));
|
|
11
|
-
const analytics = new PostgresAnalytics({ query });
|
|
12
|
-
await analytics
|
|
13
|
-
.getConversationAnalytics('conversation-1')
|
|
14
|
-
.set(analyticsKey.boolean('common.escalated'), true);
|
|
15
|
-
expect(query).toHaveBeenCalledWith(expect.stringContaining('jsonb_set'), [
|
|
16
|
-
'conversation-1',
|
|
17
|
-
'common.escalated',
|
|
18
|
-
'true',
|
|
19
|
-
]);
|
|
20
|
-
});
|
|
21
|
-
it('validates values before writing', async () => {
|
|
22
|
-
const query = vi.fn();
|
|
23
|
-
const analytics = new PostgresAnalytics({ query });
|
|
24
|
-
await expect(analytics
|
|
25
|
-
.getConversationAnalytics('conversation-1')
|
|
26
|
-
.set(analyticsKey.number('score'), 'wrong')).rejects.toThrow();
|
|
27
|
-
expect(query).not.toHaveBeenCalled();
|
|
28
|
-
});
|
|
29
|
-
it('reads JSON null as a present value', async () => {
|
|
30
|
-
const query = vi.fn().mockResolvedValue(result([{ present: true, value: null }]));
|
|
31
|
-
const analytics = new PostgresAnalytics({ query });
|
|
32
|
-
await expect(analytics
|
|
33
|
-
.getConversationAnalytics('conversation-1')
|
|
34
|
-
.get(analyticsKey.json('result', z.null()))).resolves.toBeNull();
|
|
35
|
-
});
|
|
36
|
-
it('returns a common conversation summary', async () => {
|
|
37
|
-
const startedAt = new Date('2026-08-01T10:00:00Z');
|
|
38
|
-
const endedAt = new Date('2026-08-01T10:02:00Z');
|
|
39
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
40
|
-
{
|
|
41
|
-
conversation_id: 'conversation-1',
|
|
42
|
-
agent_id: 'support-agent',
|
|
43
|
-
channel: 'phone',
|
|
44
|
-
region: 'GB',
|
|
45
|
-
started_at: startedAt,
|
|
46
|
-
ended_at: endedAt,
|
|
47
|
-
duration_seconds: 120,
|
|
48
|
-
},
|
|
49
|
-
]));
|
|
50
|
-
const analytics = new PostgresAnalytics({ query });
|
|
51
|
-
await expect(analytics.getConversationAnalytics('conversation-1').getSummary()).resolves.toEqual({
|
|
52
|
-
conversationId: 'conversation-1',
|
|
53
|
-
agentId: 'support-agent',
|
|
54
|
-
channel: 'phone',
|
|
55
|
-
region: 'GB',
|
|
56
|
-
startedAt,
|
|
57
|
-
endedAt,
|
|
58
|
-
durationSeconds: 120,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|