@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.
@@ -1,30 +1,40 @@
1
1
  import { z } from 'zod';
2
- export type AnalyticsJsonValue = boolean | number | string | null | AnalyticsJsonValue[] | {
3
- [key: string]: AnalyticsJsonValue;
4
- };
5
- export interface AnalyticsKey<T> {
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 AnalyticsValue<TKey extends AnalyticsKey<unknown>> = TKey extends AnalyticsKey<infer TValue> ? TValue : never;
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 Analytics {
16
- set<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey, value: AnalyticsValue<TKey>): Promise<void>;
17
- clear(conversationId: string, key: AnalyticsKey<unknown>): Promise<void>;
18
- get<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey): Promise<AnalyticsValue<TKey> | undefined>;
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 analyticsKey: {
22
- boolean(key: string): AnalyticsKey<boolean>;
23
- number(key: string): AnalyticsKey<number>;
24
- string(key: string): AnalyticsKey<string>;
25
- enum<const TValues extends readonly [string, ...string[]]>(key: string, values: TValues): AnalyticsKey<TValues[number]>;
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 createInMemoryAnalytics(summaries?: ConversationAnalyticsSummary[]): Analytics;
29
- export declare function assertAnalyticsJsonValue(value: unknown): asserts value is AnalyticsJsonValue;
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
@@ -1 +1 @@
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,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAClB;AAID,MAAM,WAAW,SAAS;IAGtB,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAClC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAClC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,GACV,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IAC5C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAAA;CACxF;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,CA8CjG;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CA0B5F"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
@@ -1,78 +1,114 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.analyticsKey = void 0;
3
+ exports.analyticsField = void 0;
4
+ exports.validateAgentAnalytics = validateAgentAnalytics;
5
+ exports.createAgentAnalytics = createAgentAnalytics;
4
6
  exports.createInMemoryAnalytics = createInMemoryAnalytics;
5
- exports.assertAnalyticsJsonValue = assertAnalyticsJsonValue;
7
+ exports.assertAnalyticsScalar = assertAnalyticsScalar;
6
8
  const zod_1 = require("zod");
7
- function defineKey(key, schema) {
8
- if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(key)) {
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.analyticsKey = {
14
- boolean(key) {
15
- return defineKey(key, zod_1.z.boolean());
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
- string(key) {
21
- return defineKey(key, zod_1.z.string());
16
+ number() {
17
+ return defineField('number', zod_1.z.number().finite());
22
18
  },
23
- enum(key, values) {
24
- return defineKey(key, zod_1.z.enum(values));
19
+ string() {
20
+ return defineField('string', zod_1.z.string());
25
21
  },
26
- json(key, schema) {
27
- return defineKey(key, schema);
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, key, value) {
35
- const parsed = key.schema.parse(value);
36
- assertAnalyticsJsonValue(parsed);
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(key.key, structuredClone(parsed));
81
+ values.set(fieldName, value);
39
82
  conversations.set(conversationId, values);
83
+ conversationAgents.set(conversationId, agentId);
40
84
  },
41
- async clear(conversationId, key) {
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(key.key);
90
+ values?.delete(fieldName);
44
91
  if (values?.size === 0) {
45
92
  conversations.delete(conversationId);
46
93
  }
47
94
  },
48
- async get(conversationId, key) {
49
- const values = conversations.get(conversationId);
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
- const value = values.get(key.key);
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 assertAnalyticsJsonValue(value) {
63
- if (value === null ||
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
- if (Array.isArray(value)) {
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 { Analytics } from '../analytics';
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: 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?: Record<string, string | number>;
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 declare function createAgent<TContext extends {}, TServices, TModels extends IModels>(creator: (platform: Platform<TContext, TModels>, services: TServices) => VoiceAgent): (platform: Platform<TContext, TModels>, services: TServices) => VoiceAgent;
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;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
+ {"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
- function createAgent(creator) {
5
- return creator;
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 { AnalyticsJsonValue } from './analytics';
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?: string;
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, AnalyticsJsonValue>;
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 AnalyticsField {
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: AnalyticsField[];
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":"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"}
1
+ {"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"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"}
@@ -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 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
- }
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 || undefined,
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 = optionalString(query.agentId, '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
- if (agentId)
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 { Analytics, AnalyticsKey, AnalyticsValue, ConversationAnalyticsSummary } from './analytics';
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?: string;
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 Analytics {
21
+ export declare class PostgresAnalytics implements AnalyticsStore {
22
22
  private readonly database;
23
23
  constructor(database: Queryable);
24
- set<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey, value: AnalyticsValue<TKey>): Promise<void>;
25
- clear(conversationId: string, key: AnalyticsKey<unknown>): Promise<void>;
26
- get<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey): Promise<AnalyticsValue<TKey> | undefined>;
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,SAAS,EACT,YAAY,EACZ,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,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,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;AAYD,qBAAa,iBAAkB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAE1C,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EACxC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IAoBV,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxE,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EACxC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,GACV,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;IAgBtC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC;CAmB9F;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;YAqClB,eAAe;CA0BhC;AA4DD,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"}
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, key, value) {
15
- const parsed = key.schema.parse(value);
16
- (0, analytics_1.assertAnalyticsJsonValue)(parsed);
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[$2::text], $3::jsonb, true),
18
+ SET analytics = jsonb_set(analytics, ARRAY[$3::text], $4::jsonb, true),
23
19
  updated_at = now()
24
- WHERE conversation_id = $1`, [conversationId, key.key, serialized]);
25
- assertConversationExists(result.rowCount, conversationId);
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, key) {
24
+ async clear(conversationId, agentId, fieldName) {
28
25
  const result = await this.database.query(`UPDATE conversations
29
- SET analytics = analytics - $2::text,
26
+ SET analytics = analytics - $3::text,
30
27
  updated_at = now()
31
- WHERE conversation_id = $1`, [conversationId, key.key]);
32
- assertConversationExists(result.rowCount, conversationId);
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, key) {
35
- const result = await this.database.query(`SELECT analytics ? $2::text AS present,
36
- analytics -> $2::text AS value
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`, [conversationId, key.key]);
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 key.schema.parse(result.rows[0].value);
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`, [conversationId]);
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 assertConversationExists(rowCount, conversationId) {
61
+ function assertConversationBelongsToAgent(rowCount, conversationId, agentId) {
61
62
  if (rowCount !== 1) {
62
- throw new Error(`Conversation '${conversationId}' does not exist`);
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 ?? null,
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recombine-ai/platform",
3
- "version": "0.2.6-test.7",
3
+ "version": "0.2.6-test.9",
4
4
  "description": "Recombine AI: voice agent platform",
5
5
  "license": "EULA",
6
6
  "author": "recombine.ai",