@recombine-ai/platform 0.2.6-test.3 → 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.
@@ -1,11 +1,9 @@
1
1
  import { z } from 'zod';
2
- export type AnalyticsValueType = 'boolean' | 'number' | 'string' | 'json';
3
2
  export type AnalyticsJsonValue = boolean | number | string | null | AnalyticsJsonValue[] | {
4
3
  [key: string]: AnalyticsJsonValue;
5
4
  };
6
5
  export interface AnalyticsKey<T> {
7
6
  readonly key: string;
8
- readonly valueType: AnalyticsValueType;
9
7
  readonly schema: z.ZodType<T>;
10
8
  }
11
9
  export type AnalyticsValue<TKey extends AnalyticsKey<unknown>> = TKey extends AnalyticsKey<infer TValue> ? TValue : never;
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AACzE,MAAM,MAAM,kBAAkB,GACxB,OAAO,GACP,MAAM,GACN,MAAM,GACN,IAAI,GACJ,kBAAkB,EAAE,GACpB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;CAAE,CAAA;AAE3C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;CAChC;AAED,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,IACzD,IAAI,SAAS,YAAY,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAA;AAE5D,MAAM,WAAW,4BAA4B;IACzC,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;AAED,MAAM,WAAW,qBAAqB;IAClC,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IAC7F,UAAU,IAAI,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAAA;CAClE;AAED,MAAM,WAAW,SAAS;IACtB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAAA;CAC1E;AAcD,eAAO,MAAM,YAAY;iBACR,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC;gBAG/B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;gBAG7B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;eAG9B,OAAO,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,OAChD,MAAM,UACH,OAAO,GAChB,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SAG3B,CAAC,SAAS,kBAAkB,OAAO,MAAM,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;CAGzF,CAAA;AAED,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,4BAA4B,EAAO,GAAG,SAAS,CAsCjG;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAkB5F"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,MAAM,kBAAkB,GACxB,OAAO,GACP,MAAM,GACN,MAAM,GACN,IAAI,GACJ,kBAAkB,EAAE,GACpB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;CAAE,CAAA;AAI3C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;CAChC;AAID,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,IACzD,IAAI,SAAS,YAAY,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAA;AAI5D,MAAM,WAAW,4BAA4B;IACzC,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,qBAAqB;IAClC,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IAC7F,UAAU,IAAI,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAAA;CAClE;AAID,MAAM,WAAW,SAAS;IACtB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAAA;CAC1E;AAgBD,eAAO,MAAM,YAAY;iBACR,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC;gBAG/B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;gBAG7B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;eAG9B,OAAO,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,OAChD,MAAM,UACH,OAAO,GAChB,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SAK3B,CAAC,SAAS,kBAAkB,OAAO,MAAM,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;CAKzF,CAAA;AAID,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,4BAA4B,EAAO,GAAG,SAAS,CAkDjG;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CA0B5F"}
@@ -1,28 +1,33 @@
1
- import { z } from 'zod';
2
- function defineKey(key, valueType, schema) {
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");
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
- return Object.freeze({ key, valueType, schema });
11
+ return Object.freeze({ key, schema });
7
12
  }
8
- export const analyticsKey = {
13
+ exports.analyticsKey = {
9
14
  boolean(key) {
10
- return defineKey(key, 'boolean', z.boolean());
15
+ return defineKey(key, zod_1.z.boolean());
11
16
  },
12
17
  number(key) {
13
- return defineKey(key, 'number', z.number().finite());
18
+ return defineKey(key, zod_1.z.number().finite());
14
19
  },
15
20
  string(key) {
16
- return defineKey(key, 'string', z.string());
21
+ return defineKey(key, zod_1.z.string());
17
22
  },
18
23
  enum(key, values) {
19
- return defineKey(key, 'string', z.enum(values));
24
+ return defineKey(key, zod_1.z.enum(values));
20
25
  },
21
26
  json(key, schema) {
22
- return defineKey(key, 'json', schema);
27
+ return defineKey(key, schema);
23
28
  },
24
29
  };
25
- export function createInMemoryAnalytics(summaries = []) {
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
- export function assertAnalyticsJsonValue(value) {
66
+ function assertAnalyticsJsonValue(value) {
62
67
  if (value === null ||
63
68
  typeof value === 'boolean' ||
64
69
  typeof value === 'string' ||
@@ -1,4 +1,8 @@
1
- export function createInMemoryCache() {
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
- export function createRedisCache(client, options = DEFAULT_REDIS_CACHE_OPTIONS) {
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,7 +1,8 @@
1
- export * from './interfaces/platform.js';
2
- export * from './interfaces/rc-voice.js';
3
- export * from './context-cache.js';
4
- export * from './analytics.js';
5
- export * from './postgres-analytics.js';
6
- export * from './timeouts-manager.js';
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';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAA;AACxC,cAAc,0BAA0B,CAAA;AACxC,cAAc,oBAAoB,CAAA;AAClC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,yBAAyB,CAAA;AACvC,cAAc,uBAAuB,CAAA"}
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,6 +1,23 @@
1
- export * from './interfaces/platform.js';
2
- export * from './interfaces/rc-voice.js';
3
- export * from './context-cache.js';
4
- export * from './analytics.js';
5
- export * from './postgres-analytics.js';
6
- export * from './timeouts-manager.js';
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.js';
4
- import { ContextCache } from '../context-cache.js';
5
- import { TimeoutManager } from '../timeouts-manager.js';
6
- import { IRcVoice } from './rc-voice.js';
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;AAE7B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAExC,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,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;IAE3B,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
+ {"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,3 +1,6 @@
1
- export function createAgent(creator) {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAgent = createAgent;
4
+ function createAgent(creator) {
2
5
  return creator;
3
6
  }
@@ -1 +1,2 @@
1
- export {};
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,87 @@
1
+ import { PoolConfig, QueryResult } from 'pg';
2
+ import { AnalyticsJsonValue } from './analytics';
3
+ export type AnalyticsFilterScalar = boolean | number | string;
4
+ export type AnalyticsFilterValueType = 'boolean' | 'number' | 'string';
5
+ export type AnalyticsFilterOperator = 'eq' | 'exists' | 'gt' | 'gte' | 'in' | 'lt' | 'lte';
6
+ export interface AnalyticsFilter {
7
+ key: string;
8
+ type: AnalyticsFilterValueType;
9
+ operator: AnalyticsFilterOperator;
10
+ value?: AnalyticsFilterScalar;
11
+ values?: AnalyticsFilterScalar[];
12
+ }
13
+ export type AnalyticsMetric = {
14
+ operation: 'count';
15
+ } | {
16
+ operation: 'rate';
17
+ filter: AnalyticsFilter;
18
+ } | {
19
+ operation: 'average' | 'sum';
20
+ key: string;
21
+ };
22
+ export type AnalyticsTimeGranularity = 'hour' | 'day' | 'week' | 'month';
23
+ export type AnalyticsQueryParameters = Readonly<Record<string, unknown>>;
24
+ export interface TelescopeConversation {
25
+ conversationId: string;
26
+ medium: string;
27
+ agentId?: string;
28
+ region?: string;
29
+ phoneNumber?: string;
30
+ duration?: number;
31
+ audioUrl?: string;
32
+ createdAt: number;
33
+ endedAt?: number;
34
+ analytics: Record<string, AnalyticsJsonValue>;
35
+ }
36
+ export interface TelescopeConversationPage {
37
+ limit: number;
38
+ items: TelescopeConversation[];
39
+ lastConversationId?: string;
40
+ }
41
+ export interface AnalyticsFieldValue {
42
+ value: AnalyticsFilterScalar;
43
+ count: number;
44
+ }
45
+ export interface AnalyticsField {
46
+ key: string;
47
+ type: AnalyticsFilterValueType;
48
+ variantCount: number;
49
+ values: AnalyticsFieldValue[];
50
+ }
51
+ export interface AnalyticsFields {
52
+ items: AnalyticsField[];
53
+ }
54
+ export interface AnalyticsTimeSeriesItem {
55
+ startAt: string;
56
+ value: number | null;
57
+ numerator?: number;
58
+ denominator?: number;
59
+ }
60
+ export interface AnalyticsTimeSeries {
61
+ metric: AnalyticsMetric;
62
+ granularity: AnalyticsTimeGranularity;
63
+ from: string;
64
+ to: string;
65
+ items: AnalyticsTimeSeriesItem[];
66
+ }
67
+ interface Queryable {
68
+ query<TResult extends Record<string, unknown> = Record<string, unknown>>(text: string, values?: unknown[]): Promise<QueryResult<TResult>>;
69
+ }
70
+ export declare class AnalyticsQueryError extends Error {
71
+ name: string;
72
+ }
73
+ export declare class PostgresAnalyticsQueryService {
74
+ private readonly database;
75
+ constructor(database: Queryable);
76
+ getConversation(conversationId: string): Promise<TelescopeConversation | undefined>;
77
+ getConversations(query: AnalyticsQueryParameters): Promise<TelescopeConversationPage>;
78
+ getAnalyticsFields(query: AnalyticsQueryParameters): Promise<AnalyticsFields>;
79
+ getTimeSeries(query: AnalyticsQueryParameters): Promise<AnalyticsTimeSeries>;
80
+ }
81
+ export interface PostgresAnalyticsQueryServices {
82
+ queries: PostgresAnalyticsQueryService;
83
+ close(): Promise<void>;
84
+ }
85
+ export declare function createPostgresAnalyticsQueryServices(config: PoolConfig): PostgresAnalyticsQueryServices;
86
+ export {};
87
+ //# sourceMappingURL=postgres-analytics-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,388 @@
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 {
7
+ name = 'AnalyticsQueryError';
8
+ }
9
+ exports.AnalyticsQueryError = AnalyticsQueryError;
10
+ class PostgresAnalyticsQueryService {
11
+ database;
12
+ constructor(database) {
13
+ this.database = database;
14
+ }
15
+ async getConversation(conversationId) {
16
+ const result = await this.database.query(`SELECT c.conversation_id, c.agent_id, c.channel, c.region, c.started_at,
17
+ c.ended_at, c.duration_seconds, c.analytics,
18
+ p.phone_number, p.recording_url
19
+ FROM conversations c
20
+ LEFT JOIN phone_calls p ON p.conversation_id = c.conversation_id
21
+ WHERE c.conversation_id = $1`, [conversationId]);
22
+ const row = result.rows[0];
23
+ return row ? conversationRowToJson(row) : undefined;
24
+ }
25
+ async getConversations(query) {
26
+ const parsedLimit = Number.parseInt(optionalString(query.limit, 'limit') ?? '10', 10);
27
+ const requestedLimit = Number.isFinite(parsedLimit)
28
+ ? Math.min(100, Math.max(1, parsedLimit))
29
+ : 10;
30
+ const cursor = parseConversationCursor(optionalString(query.lastConversationId, 'lastConversationId'));
31
+ const { bind, conditions, values } = buildConversationConditions(query);
32
+ if (cursor) {
33
+ conditions.push(`(c.started_at, c.conversation_id) < (${bind(cursor.startedAt)}, ${bind(cursor.conversationId)})`);
34
+ }
35
+ const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
36
+ const limitParameter = bind(requestedLimit + 1);
37
+ const result = await this.database.query(`SELECT c.conversation_id, c.agent_id, c.channel, c.region, c.started_at,
38
+ c.ended_at, c.duration_seconds, c.analytics,
39
+ p.phone_number, p.recording_url
40
+ FROM conversations c
41
+ LEFT JOIN phone_calls p ON p.conversation_id = c.conversation_id
42
+ ${where}
43
+ ORDER BY c.started_at DESC, c.conversation_id DESC
44
+ LIMIT ${limitParameter}`, values);
45
+ const hasNextPage = result.rows.length > requestedLimit;
46
+ const rows = result.rows.slice(0, requestedLimit);
47
+ return {
48
+ limit: requestedLimit,
49
+ items: rows.map(conversationRowToJson),
50
+ lastConversationId: hasNextPage && rows.length > 0 ? makeConversationCursor(rows.at(-1)) : undefined,
51
+ };
52
+ }
53
+ async getAnalyticsFields(query) {
54
+ const values = [];
55
+ let where = "WHERE jsonb_typeof(entry.value) IN ('boolean', 'number', 'string')";
56
+ const agentId = optionalString(query.agentId, 'agentId');
57
+ if (agentId) {
58
+ values.push(agentId);
59
+ where += ` AND c.agent_id = $${values.length}`;
60
+ }
61
+ const result = await this.database.query(`WITH value_counts AS (
62
+ SELECT entry.key,
63
+ jsonb_typeof(entry.value) AS type,
64
+ entry.value AS value,
65
+ COUNT(*)::bigint AS occurrence_count
66
+ FROM conversations c
67
+ CROSS JOIN LATERAL jsonb_each(c.analytics) AS entry(key, value)
68
+ ${where}
69
+ GROUP BY entry.key, type, entry.value
70
+ ),
71
+ ranked AS (
72
+ SELECT key,
73
+ type,
74
+ value,
75
+ occurrence_count,
76
+ COUNT(*) OVER (PARTITION BY key, type)::integer AS variant_count,
77
+ ROW_NUMBER() OVER (
78
+ PARTITION BY key, type
79
+ ORDER BY occurrence_count DESC, value::text ASC
80
+ ) AS rank
81
+ FROM value_counts
82
+ )
83
+ SELECT key, type, value, occurrence_count, variant_count
84
+ FROM ranked
85
+ WHERE rank <= 50
86
+ ORDER BY key ASC, type ASC, rank ASC`, values);
87
+ return analyticsFieldRowsToJson(result.rows);
88
+ }
89
+ async getTimeSeries(query) {
90
+ const metric = parseMetric(requiredString(query.metric, 'metric'));
91
+ const granularity = (optionalString(query.granularity, 'granularity') ??
92
+ 'day');
93
+ if (!['hour', 'day', 'week', 'month'].includes(granularity)) {
94
+ throw new AnalyticsQueryError('granularity must be hour, day, week, or month');
95
+ }
96
+ const { bind, conditions, from, to, values } = buildConversationConditions(query);
97
+ if (!from || !to) {
98
+ throw new AnalyticsQueryError('from and to are required for a time series');
99
+ }
100
+ const bucketExpression = `date_trunc(${bind(granularity)}::text, c.started_at, 'UTC')`;
101
+ let valueExpression;
102
+ let numeratorExpression = 'NULL::bigint';
103
+ if (metric.operation === 'count') {
104
+ valueExpression = 'COUNT(*)::double precision';
105
+ }
106
+ else if (metric.operation === 'rate') {
107
+ const predicate = makeAnalyticsPredicate(metric.filter, bind);
108
+ numeratorExpression = `COUNT(*) FILTER (WHERE ${predicate})::bigint`;
109
+ valueExpression =
110
+ `COUNT(*) FILTER (WHERE ${predicate})::double precision / ` + 'NULLIF(COUNT(*), 0)';
111
+ }
112
+ else {
113
+ const key = bind(metric.key);
114
+ const numericValue = `CASE WHEN jsonb_typeof(c.analytics -> (${key}::text)) = 'number' ` +
115
+ `THEN (c.analytics ->> (${key}::text))::double precision END`;
116
+ valueExpression =
117
+ metric.operation === 'average' ? `AVG(${numericValue})` : `SUM(${numericValue})`;
118
+ }
119
+ const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : '';
120
+ const result = await this.database.query(`SELECT ${bucketExpression} AS bucket_start,
121
+ ${valueExpression} AS metric_value,
122
+ ${numeratorExpression} AS numerator,
123
+ COUNT(*)::bigint AS denominator
124
+ FROM conversations c
125
+ LEFT JOIN phone_calls p ON p.conversation_id = c.conversation_id
126
+ ${where}
127
+ GROUP BY bucket_start
128
+ ORDER BY bucket_start ASC`, values);
129
+ return {
130
+ metric,
131
+ granularity,
132
+ from: from.toISOString(),
133
+ to: to.toISOString(),
134
+ items: result.rows.map((row) => ({
135
+ startAt: new Date(row.bucket_start).toISOString(),
136
+ value: row.metric_value === null ? null : Number(row.metric_value),
137
+ ...(metric.operation === 'rate'
138
+ ? {
139
+ numerator: Number(row.numerator),
140
+ denominator: Number(row.denominator),
141
+ }
142
+ : {}),
143
+ })),
144
+ };
145
+ }
146
+ }
147
+ exports.PostgresAnalyticsQueryService = PostgresAnalyticsQueryService;
148
+ function createPostgresAnalyticsQueryServices(config) {
149
+ const pool = new pg_1.Pool(config);
150
+ return {
151
+ queries: new PostgresAnalyticsQueryService(pool),
152
+ close: () => pool.end(),
153
+ };
154
+ }
155
+ function optionalString(value, name) {
156
+ if (value === undefined)
157
+ return undefined;
158
+ if (typeof value !== 'string') {
159
+ throw new AnalyticsQueryError(`${name} must be a string`);
160
+ }
161
+ return value;
162
+ }
163
+ function requiredString(value, name) {
164
+ const result = optionalString(value, name);
165
+ if (!result) {
166
+ throw new AnalyticsQueryError(`${name} is required`);
167
+ }
168
+ return result;
169
+ }
170
+ function parseDateFilter(value, name) {
171
+ const stringValue = optionalString(value, name);
172
+ if (stringValue === undefined)
173
+ return undefined;
174
+ const date = /^\d+$/.test(stringValue) ? new Date(Number(stringValue)) : new Date(stringValue);
175
+ if (Number.isNaN(date.getTime())) {
176
+ throw new AnalyticsQueryError(`${name} must be a valid date`);
177
+ }
178
+ return date;
179
+ }
180
+ function parseDurationFilter(value, name) {
181
+ const stringValue = optionalString(value, name);
182
+ if (stringValue === undefined)
183
+ return undefined;
184
+ const duration = Number(stringValue);
185
+ if (!Number.isFinite(duration) || duration < 0) {
186
+ throw new AnalyticsQueryError(`${name} must be a non-negative number`);
187
+ }
188
+ return duration;
189
+ }
190
+ function parseAnalyticsFilters(value) {
191
+ const stringValue = optionalString(value, 'filters');
192
+ if (!stringValue)
193
+ return [];
194
+ let filters;
195
+ try {
196
+ filters = JSON.parse(stringValue);
197
+ }
198
+ catch {
199
+ throw new AnalyticsQueryError('filters must be valid JSON');
200
+ }
201
+ if (!Array.isArray(filters) || filters.length > 20) {
202
+ throw new AnalyticsQueryError('filters must be an array with at most 20 entries');
203
+ }
204
+ return filters.map(validateAnalyticsFilter);
205
+ }
206
+ function validateAnalyticsFilter(value) {
207
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
208
+ throw new AnalyticsQueryError('Each filter must be an object');
209
+ }
210
+ const filter = value;
211
+ if (typeof filter.key !== 'string' || !/^[A-Za-z0-9_.:-]{1,128}$/.test(filter.key)) {
212
+ throw new AnalyticsQueryError('Filter key is invalid');
213
+ }
214
+ if (!['boolean', 'number', 'string'].includes(String(filter.type))) {
215
+ throw new AnalyticsQueryError(`Filter '${filter.key}' has an invalid type`);
216
+ }
217
+ const type = filter.type;
218
+ const allowedOperators = type === 'number'
219
+ ? ['eq', 'in', 'gt', 'gte', 'lt', 'lte', 'exists']
220
+ : ['eq', 'in', 'exists'];
221
+ if (!allowedOperators.includes(String(filter.operator))) {
222
+ throw new AnalyticsQueryError(`Filter '${filter.key}' has an invalid operator`);
223
+ }
224
+ const operator = filter.operator;
225
+ if (operator === 'in') {
226
+ if (!Array.isArray(filter.values) ||
227
+ filter.values.length === 0 ||
228
+ filter.values.length > 50 ||
229
+ filter.values.some((item) => typeof item !== type || (type === 'number' && !Number.isFinite(item)))) {
230
+ throw new AnalyticsQueryError(`Filter '${filter.key}' has invalid values`);
231
+ }
232
+ return { key: filter.key, type, operator, values: filter.values };
233
+ }
234
+ if (operator !== 'exists' && typeof filter.value !== type) {
235
+ throw new AnalyticsQueryError(`Filter '${filter.key}' has an invalid value`);
236
+ }
237
+ if (type === 'number' && !Number.isFinite(filter.value)) {
238
+ throw new AnalyticsQueryError(`Filter '${filter.key}' has an invalid value`);
239
+ }
240
+ return {
241
+ key: filter.key,
242
+ type,
243
+ operator,
244
+ ...(operator === 'exists' ? {} : { value: filter.value }),
245
+ };
246
+ }
247
+ function parseMetric(value) {
248
+ let metric;
249
+ try {
250
+ metric = JSON.parse(value);
251
+ }
252
+ catch {
253
+ throw new AnalyticsQueryError('metric must be valid JSON');
254
+ }
255
+ if (!metric || typeof metric !== 'object' || Array.isArray(metric)) {
256
+ throw new AnalyticsQueryError('metric must be an object');
257
+ }
258
+ const record = metric;
259
+ if (!['count', 'rate', 'average', 'sum'].includes(String(record.operation))) {
260
+ throw new AnalyticsQueryError('metric operation is invalid');
261
+ }
262
+ if (record.operation === 'rate') {
263
+ return { operation: 'rate', filter: validateAnalyticsFilter(record.filter) };
264
+ }
265
+ if (record.operation === 'average' || record.operation === 'sum') {
266
+ if (typeof record.key !== 'string' || !/^[A-Za-z0-9_.:-]{1,128}$/.test(record.key)) {
267
+ throw new AnalyticsQueryError('Metric key is invalid');
268
+ }
269
+ return { operation: record.operation, key: record.key };
270
+ }
271
+ return { operation: 'count' };
272
+ }
273
+ function parseConversationCursor(value) {
274
+ if (!value)
275
+ return undefined;
276
+ try {
277
+ const cursor = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
278
+ const startedAt = new Date(String(cursor.startedAt));
279
+ if (Number.isNaN(startedAt.getTime()) ||
280
+ typeof cursor.conversationId !== 'string' ||
281
+ cursor.conversationId === '') {
282
+ throw new Error('invalid cursor');
283
+ }
284
+ return { startedAt, conversationId: cursor.conversationId };
285
+ }
286
+ catch {
287
+ throw new AnalyticsQueryError('lastConversationId is invalid');
288
+ }
289
+ }
290
+ function makeConversationCursor(row) {
291
+ return Buffer.from(JSON.stringify({
292
+ startedAt: new Date(row.started_at).toISOString(),
293
+ conversationId: row.conversation_id,
294
+ })).toString('base64url');
295
+ }
296
+ function conversationRowToJson(row) {
297
+ return {
298
+ conversationId: row.conversation_id,
299
+ medium: row.channel,
300
+ agentId: row.agent_id || undefined,
301
+ region: row.region || undefined,
302
+ phoneNumber: row.phone_number || undefined,
303
+ duration: row.duration_seconds ?? undefined,
304
+ audioUrl: row.recording_url || undefined,
305
+ createdAt: new Date(row.started_at).getTime(),
306
+ endedAt: row.ended_at ? new Date(row.ended_at).getTime() : undefined,
307
+ analytics: row.analytics ?? {},
308
+ };
309
+ }
310
+ function analyticsFieldRowsToJson(rows) {
311
+ const fields = new Map();
312
+ for (const row of rows) {
313
+ const id = `${row.key}\u0000${row.type}`;
314
+ let field = fields.get(id);
315
+ if (!field) {
316
+ field = {
317
+ key: row.key,
318
+ type: row.type,
319
+ variantCount: Number(row.variant_count),
320
+ values: [],
321
+ };
322
+ fields.set(id, field);
323
+ }
324
+ if (field.variantCount <= 50) {
325
+ field.values.push({ value: row.value, count: Number(row.occurrence_count) });
326
+ }
327
+ }
328
+ return { items: [...fields.values()] };
329
+ }
330
+ function buildConversationConditions(query) {
331
+ const values = [];
332
+ const bind = (value) => {
333
+ values.push(value);
334
+ return `$${values.length}`;
335
+ };
336
+ const conditions = [];
337
+ const from = parseDateFilter(query.from, 'from');
338
+ const to = parseDateFilter(query.to, 'to');
339
+ if (from && to && from >= to) {
340
+ throw new AnalyticsQueryError('from must be earlier than to');
341
+ }
342
+ const agentId = optionalString(query.agentId, 'agentId');
343
+ const region = optionalString(query.region, 'region');
344
+ const channel = optionalString(query.channel, 'channel');
345
+ const phoneNumber = optionalString(query.phoneNumber, 'phoneNumber');
346
+ if (agentId)
347
+ conditions.push(`c.agent_id = ${bind(agentId)}`);
348
+ if (region)
349
+ conditions.push(`c.region = ${bind(region)}`);
350
+ if (channel)
351
+ conditions.push(`c.channel = ${bind(channel)}`);
352
+ if (phoneNumber)
353
+ conditions.push(`p.phone_number = ${bind(phoneNumber)}`);
354
+ if (from)
355
+ conditions.push(`c.started_at >= ${bind(from)}`);
356
+ if (to)
357
+ conditions.push(`c.started_at < ${bind(to)}`);
358
+ const minDuration = parseDurationFilter(query.minDurationSeconds, 'minDurationSeconds');
359
+ const maxDuration = parseDurationFilter(query.maxDurationSeconds, 'maxDurationSeconds');
360
+ if (minDuration !== undefined)
361
+ conditions.push(`c.duration_seconds >= ${bind(minDuration)}`);
362
+ if (maxDuration !== undefined)
363
+ conditions.push(`c.duration_seconds <= ${bind(maxDuration)}`);
364
+ if (minDuration !== undefined && maxDuration !== undefined && minDuration > maxDuration) {
365
+ throw new AnalyticsQueryError('minDurationSeconds must not exceed maxDurationSeconds');
366
+ }
367
+ for (const filter of parseAnalyticsFilters(query.filters)) {
368
+ conditions.push(makeAnalyticsPredicate(filter, bind));
369
+ }
370
+ return { bind, conditions, from, to, values };
371
+ }
372
+ function makeAnalyticsPredicate(filter, bind) {
373
+ const key = bind(filter.key);
374
+ if (filter.operator === 'exists') {
375
+ return `c.analytics ? (${key}::text)`;
376
+ }
377
+ if (filter.operator === 'eq') {
378
+ return `c.analytics @> jsonb_build_object(${key}::text, ${bind(JSON.stringify(filter.value))}::jsonb)`;
379
+ }
380
+ if (filter.operator === 'in') {
381
+ const predicates = filter.values.map((value) => `c.analytics @> jsonb_build_object(${key}::text, ${bind(JSON.stringify(value))}::jsonb)`);
382
+ return `(${predicates.join(' OR ')})`;
383
+ }
384
+ const operator = { gt: '>', gte: '>=', lt: '<', lte: '<=' }[filter.operator];
385
+ return (`(CASE WHEN jsonb_typeof(c.analytics -> (${key}::text)) = 'number' THEN ` +
386
+ `(c.analytics ->> (${key}::text))::double precision ${operator} ${bind(filter.value)} ` +
387
+ 'ELSE false END)');
388
+ }
@@ -1,5 +1,6 @@
1
1
  import { Pool, PoolConfig, QueryResult } from 'pg';
2
- import { Analytics, ConversationAnalytics } from './analytics.js';
2
+ import { Analytics, ConversationAnalytics } from './analytics';
3
+ import { PostgresAnalyticsQueryService } from './postgres-analytics-query';
3
4
  export interface ConversationRecord {
4
5
  conversationId: string;
5
6
  agentId?: string;
@@ -34,6 +35,7 @@ export declare function migrateAnalyticsDatabase(pool: Pool): Promise<void>;
34
35
  export interface PostgresAnalyticsServices {
35
36
  analytics: PostgresAnalytics;
36
37
  conversations: PostgresConversationRepository;
38
+ queries: PostgresAnalyticsQueryService;
37
39
  close(): Promise<void>;
38
40
  }
39
41
  export declare function createPostgresAnalyticsServices(config: PoolConfig): Promise<PostgresAnalyticsServices>;
@@ -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,gBAAgB,CAAA;AAEvB,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;AAED,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;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;AAYD,qBAAa,iBAAkB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAEhD,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB;CA6E1E;AAQD,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;YAiBF,kBAAkB;YAkClB,eAAe;CAoBhC;AA+CD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCxE;AAED,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,8BAA8B,CAAA;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,wBAAsB,+BAA+B,CACjD,MAAM,EAAE,UAAU,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAapC"}
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,6 +1,12 @@
1
- import { Pool } from 'pg';
2
- import { assertAnalyticsJsonValue, } from './analytics.js';
3
- export class PostgresAnalytics {
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 {
4
10
  database;
5
11
  constructor(database) {
6
12
  this.database = database;
@@ -10,7 +16,7 @@ export class PostgresAnalytics {
10
16
  return {
11
17
  async set(key, value) {
12
18
  const parsed = key.schema.parse(value);
13
- assertAnalyticsJsonValue(parsed);
19
+ (0, analytics_1.assertAnalyticsJsonValue)(parsed);
14
20
  const serialized = JSON.stringify(parsed);
15
21
  if (serialized === undefined) {
16
22
  throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
@@ -65,12 +71,13 @@ export class PostgresAnalytics {
65
71
  };
66
72
  }
67
73
  }
74
+ exports.PostgresAnalytics = PostgresAnalytics;
68
75
  function assertConversationExists(rowCount, conversationId) {
69
76
  if (rowCount !== 1) {
70
77
  throw new Error(`Conversation '${conversationId}' does not exist`);
71
78
  }
72
79
  }
73
- export class PostgresConversationRepository {
80
+ class PostgresConversationRepository {
74
81
  pool;
75
82
  constructor(pool) {
76
83
  this.pool = pool;
@@ -138,10 +145,13 @@ export class PostgresConversationRepository {
138
145
  ]);
139
146
  }
140
147
  }
148
+ exports.PostgresConversationRepository = PostgresConversationRepository;
141
149
  const migrations = [
142
150
  {
143
151
  version: 1,
144
152
  sql: `
153
+ -- Analytics rationale: This is the channel-neutral parent row and top-level JSONB map.
154
+ -- Removing it leaves typed values with no lifecycle or common dimensions to attach to.
145
155
  CREATE TABLE conversations (
146
156
  conversation_id text PRIMARY KEY,
147
157
  agent_id text,
@@ -159,6 +169,8 @@ const migrations = [
159
169
  updated_at timestamptz NOT NULL DEFAULT now()
160
170
  );
161
171
 
172
+ -- Analytics rationale: Phone-only data stays in a one-to-one child so text conversations
173
+ -- do not acquire call-specific columns or assumptions.
162
174
  CREATE TABLE phone_calls (
163
175
  conversation_id text PRIMARY KEY REFERENCES conversations(conversation_id)
164
176
  ON DELETE CASCADE,
@@ -167,6 +179,8 @@ const migrations = [
167
179
  ended_reason text
168
180
  );
169
181
 
182
+ -- Analytics rationale: These indexes cover time-ordered listing and its common equality
183
+ -- filters; without them dashboards and Telescope lists degrade to full table scans.
170
184
  CREATE INDEX conversations_started_at_idx
171
185
  ON conversations (started_at DESC, conversation_id DESC);
172
186
  CREATE INDEX conversations_agent_started_at_idx
@@ -175,6 +189,9 @@ const migrations = [
175
189
  ON conversations (region, started_at DESC, conversation_id DESC);
176
190
  CREATE INDEX conversations_channel_started_at_idx
177
191
  ON conversations (channel, started_at DESC, conversation_id DESC);
192
+
193
+ -- Analytics rationale: GIN accelerates JSONB containment filters while the phone index
194
+ -- supports exact caller lookups joined through the conversation ID.
178
195
  CREATE INDEX conversations_analytics_idx
179
196
  ON conversations USING gin (analytics);
180
197
  CREATE INDEX phone_calls_phone_number_idx
@@ -182,7 +199,7 @@ const migrations = [
182
199
  `,
183
200
  },
184
201
  ];
185
- export async function migrateAnalyticsDatabase(pool) {
202
+ async function migrateAnalyticsDatabase(pool) {
186
203
  const client = await pool.connect();
187
204
  try {
188
205
  await client.query("SELECT pg_advisory_lock(hashtext('recombine_analytics_migrations'))");
@@ -216,8 +233,8 @@ export async function migrateAnalyticsDatabase(pool) {
216
233
  client.release();
217
234
  }
218
235
  }
219
- export async function createPostgresAnalyticsServices(config) {
220
- const pool = new Pool(config);
236
+ async function createPostgresAnalyticsServices(config) {
237
+ const pool = new pg_1.Pool(config);
221
238
  try {
222
239
  await migrateAnalyticsDatabase(pool);
223
240
  }
@@ -228,6 +245,7 @@ export async function createPostgresAnalyticsServices(config) {
228
245
  return {
229
246
  analytics: new PostgresAnalytics(pool),
230
247
  conversations: new PostgresConversationRepository(pool),
248
+ queries: new postgres_analytics_query_1.PostgresAnalyticsQueryService(pool),
231
249
  close: () => pool.end(),
232
250
  };
233
251
  }
@@ -1,5 +1,5 @@
1
1
  import { Logger } from '@recombine-ai/engine';
2
- import { ContextCache } from './context-cache.js';
2
+ import { ContextCache } from './context-cache';
3
3
  export type TimeoutsState = {
4
4
  timeouts: Record<string, number>;
5
5
  };
@@ -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;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAKjD,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
+ {"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
- export class TimeoutManager {
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",
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
- "type": "module",
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.2.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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=analytics.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"analytics.spec.d.ts","sourceRoot":"","sources":["../src/analytics.spec.ts"],"names":[],"mappings":""}
@@ -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,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=postgres-analytics.spec.d.ts.map
@@ -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
- });