@absolutejs/voice 0.0.22-beta.514 → 0.0.22-beta.515

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.
@@ -0,0 +1,29 @@
1
+ import type { VoiceCallerIdentity } from "./callerMemory";
2
+ import type { VoiceCRMContactSummary, VoiceCRMContract } from "./crmContract";
3
+ export type VoiceCallerCRMLinkRecord = {
4
+ callerKey: string;
5
+ vendor: string;
6
+ contactId: string;
7
+ contact: VoiceCRMContactSummary;
8
+ resolvedAt: number;
9
+ source: "memory" | "phone-lookup" | "email-lookup" | "manual";
10
+ };
11
+ export type VoiceCallerCRMLinkCacheStore = {
12
+ get(key: string): Promise<VoiceCallerCRMLinkRecord | null> | VoiceCallerCRMLinkRecord | null;
13
+ put(record: VoiceCallerCRMLinkRecord): Promise<void> | void;
14
+ remove(key: string): Promise<boolean> | boolean;
15
+ };
16
+ export type CreateVoiceCallerCRMLinkerOptions = {
17
+ contract: VoiceCRMContract;
18
+ cache?: VoiceCallerCRMLinkCacheStore;
19
+ staleAfterMs?: number;
20
+ now?: () => number;
21
+ };
22
+ export declare const createInMemoryVoiceCallerCRMLinkCache: () => VoiceCallerCRMLinkCacheStore;
23
+ export declare const createVoiceCallerCRMLinker: (options: CreateVoiceCallerCRMLinkerOptions) => {
24
+ associate: (identity: VoiceCallerIdentity, contact: VoiceCRMContactSummary) => Promise<VoiceCallerCRMLinkRecord>;
25
+ contract: VoiceCRMContract;
26
+ invalidate: (identity: VoiceCallerIdentity) => Promise<boolean>;
27
+ resolve: (identity: VoiceCallerIdentity) => Promise<VoiceCallerCRMLinkRecord | null>;
28
+ };
29
+ export type VoiceCallerCRMLinker = ReturnType<typeof createVoiceCallerCRMLinker>;
@@ -0,0 +1,37 @@
1
+ import type { VoiceCRMContract } from "./crmContract";
2
+ export type VoiceCRMCallLoggerInput = {
3
+ sessionId: string;
4
+ contactId?: string;
5
+ startedAt: number;
6
+ endedAt?: number;
7
+ durationSeconds?: number;
8
+ summary?: string;
9
+ disposition?: string;
10
+ recordingUrl?: string;
11
+ transcriptUrl?: string;
12
+ metadata?: Record<string, unknown>;
13
+ };
14
+ export type VoiceCRMCallLogResult = {
15
+ activityId: string;
16
+ vendor: string;
17
+ loggedAt: number;
18
+ };
19
+ export type VoiceCRMCallLogErrorPolicy = "throw" | "swallow" | "queue";
20
+ export type CreateVoiceCRMCallLoggerOptions = {
21
+ contract: VoiceCRMContract;
22
+ errorPolicy?: VoiceCRMCallLogErrorPolicy;
23
+ onError?: (error: Error, input: VoiceCRMCallLoggerInput) => void | Promise<void>;
24
+ enqueueOnFailure?: (input: VoiceCRMCallLoggerInput) => Promise<void> | void;
25
+ now?: () => number;
26
+ };
27
+ export declare const createVoiceCRMCallLogger: (options: CreateVoiceCRMCallLoggerOptions) => {
28
+ contract: VoiceCRMContract;
29
+ logCallEnd: (input: VoiceCRMCallLoggerInput) => Promise<VoiceCRMCallLogResult | null>;
30
+ noteOnContact: (input: {
31
+ contactId: string;
32
+ body: string;
33
+ }) => Promise<{
34
+ noteId: string;
35
+ } | null>;
36
+ };
37
+ export type VoiceCRMCallLogger = ReturnType<typeof createVoiceCRMCallLogger>;
@@ -0,0 +1,70 @@
1
+ export type VoiceCRMContactSummary = {
2
+ id: string;
3
+ vendor?: string;
4
+ firstName?: string;
5
+ lastName?: string;
6
+ email?: string;
7
+ phone?: string;
8
+ jobTitle?: string;
9
+ metadata?: Record<string, unknown>;
10
+ };
11
+ export type VoiceCRMLeadInput = {
12
+ firstName?: string;
13
+ lastName?: string;
14
+ email?: string;
15
+ phone?: string;
16
+ company?: string;
17
+ jobTitle?: string;
18
+ source?: string;
19
+ notes?: string;
20
+ metadata?: Record<string, unknown>;
21
+ };
22
+ export type VoiceCRMCallActivityInput = {
23
+ contactId?: string;
24
+ sessionId: string;
25
+ startedAt: number;
26
+ endedAt: number;
27
+ durationSeconds: number;
28
+ summary?: string;
29
+ disposition?: string;
30
+ recordingUrl?: string;
31
+ transcriptUrl?: string;
32
+ metadata?: Record<string, unknown>;
33
+ };
34
+ export type VoiceCRMNoteInput = {
35
+ contactId: string;
36
+ body: string;
37
+ metadata?: Record<string, unknown>;
38
+ };
39
+ export type VoiceCRMTaskInput = {
40
+ contactId?: string;
41
+ subject: string;
42
+ description?: string;
43
+ dueAt?: number;
44
+ priority?: "low" | "normal" | "high";
45
+ };
46
+ export type VoiceCRMContract = {
47
+ readonly vendor: string;
48
+ lookupByPhone(phone: string): Promise<VoiceCRMContactSummary | null>;
49
+ lookupByEmail(email: string): Promise<VoiceCRMContactSummary | null>;
50
+ createLead(input: VoiceCRMLeadInput): Promise<VoiceCRMContactSummary>;
51
+ logCall(input: VoiceCRMCallActivityInput): Promise<{
52
+ activityId: string;
53
+ }>;
54
+ addNote(input: VoiceCRMNoteInput): Promise<{
55
+ noteId: string;
56
+ }>;
57
+ createTask?(input: VoiceCRMTaskInput): Promise<{
58
+ taskId: string;
59
+ }>;
60
+ };
61
+ export type VoiceCRMRegistry = {
62
+ default(): VoiceCRMContract | null;
63
+ get(vendor: string): VoiceCRMContract | null;
64
+ list(): VoiceCRMContract[];
65
+ };
66
+ export type CreateVoiceCRMRegistryOptions = {
67
+ contracts: VoiceCRMContract[];
68
+ defaultVendor?: string;
69
+ };
70
+ export declare const createVoiceCRMRegistry: (options: CreateVoiceCRMRegistryOptions) => VoiceCRMRegistry;
package/dist/index.d.ts CHANGED
@@ -351,4 +351,10 @@ export { compileVoicePathwayToAssistant } from "./pathwayCompiler";
351
351
  export type { CompileVoicePathwayOptions, VoicePathwayCompiledAssistant, VoicePathwayCompilerToolDefinition, } from "./pathwayCompiler";
352
352
  export { renderVoicePathwayMermaid, renderVoicePathwayText, visualizeVoicePathway, } from "./pathwayVisualizer";
353
353
  export type { VoicePathwayVisualization } from "./pathwayVisualizer";
354
+ export { createVoiceCRMRegistry } from "./crmContract";
355
+ export type { CreateVoiceCRMRegistryOptions, VoiceCRMCallActivityInput, VoiceCRMContactSummary, VoiceCRMContract, VoiceCRMLeadInput, VoiceCRMNoteInput, VoiceCRMRegistry, VoiceCRMTaskInput, } from "./crmContract";
356
+ export { createInMemoryVoiceCallerCRMLinkCache, createVoiceCallerCRMLinker, } from "./callerCRMLinker";
357
+ export type { CreateVoiceCallerCRMLinkerOptions, VoiceCallerCRMLinkCacheStore, VoiceCallerCRMLinker, VoiceCallerCRMLinkRecord, } from "./callerCRMLinker";
358
+ export { createVoiceCRMCallLogger } from "./crmCallLogger";
359
+ export type { CreateVoiceCRMCallLoggerOptions, VoiceCRMCallLogErrorPolicy, VoiceCRMCallLogger, VoiceCRMCallLoggerInput, VoiceCRMCallLogResult, } from "./crmCallLogger";
354
360
  export * from "./types";
package/dist/index.js CHANGED
@@ -51360,6 +51360,159 @@ var visualizeVoicePathway = (pathway) => ({
51360
51360
  mermaid: renderVoicePathwayMermaid(pathway),
51361
51361
  text: renderVoicePathwayText(pathway)
51362
51362
  });
51363
+ // src/crmContract.ts
51364
+ var createVoiceCRMRegistry = (options) => {
51365
+ const byVendor = new Map;
51366
+ for (const contract of options.contracts) {
51367
+ byVendor.set(contract.vendor, contract);
51368
+ }
51369
+ const defaultVendor = options.defaultVendor ?? options.contracts[0]?.vendor ?? null;
51370
+ return {
51371
+ default() {
51372
+ return defaultVendor ? byVendor.get(defaultVendor) ?? null : null;
51373
+ },
51374
+ get(vendor) {
51375
+ return byVendor.get(vendor) ?? null;
51376
+ },
51377
+ list() {
51378
+ return Array.from(byVendor.values());
51379
+ }
51380
+ };
51381
+ };
51382
+ // src/callerCRMLinker.ts
51383
+ var cacheKeyFor = (identity, vendor) => {
51384
+ const id = identity.externalId ?? identity.phone ?? identity.email ?? "anonymous";
51385
+ return `${vendor}::${id}`;
51386
+ };
51387
+ var createInMemoryVoiceCallerCRMLinkCache = () => {
51388
+ const store = new Map;
51389
+ return {
51390
+ get: (key) => store.get(key) ?? null,
51391
+ put: (record) => {
51392
+ store.set(record.callerKey, { ...record });
51393
+ },
51394
+ remove: (key) => store.delete(key)
51395
+ };
51396
+ };
51397
+ var createVoiceCallerCRMLinker = (options) => {
51398
+ const now = options.now ?? (() => Date.now());
51399
+ const cache = options.cache ?? createInMemoryVoiceCallerCRMLinkCache();
51400
+ const staleAfter = options.staleAfterMs ?? 24 * 60 * 60 * 1000;
51401
+ const isFresh = (record) => now() - record.resolvedAt < staleAfter;
51402
+ const resolve3 = async (identity) => {
51403
+ const key = cacheKeyFor(identity, options.contract.vendor);
51404
+ const cached = await Promise.resolve(cache.get(key));
51405
+ if (cached && isFresh(cached)) {
51406
+ return cached;
51407
+ }
51408
+ let contact = null;
51409
+ let source = null;
51410
+ if (identity.phone) {
51411
+ contact = await options.contract.lookupByPhone(identity.phone);
51412
+ if (contact)
51413
+ source = "phone-lookup";
51414
+ }
51415
+ if (!contact && identity.email) {
51416
+ contact = await options.contract.lookupByEmail(identity.email);
51417
+ if (contact)
51418
+ source = "email-lookup";
51419
+ }
51420
+ if (!contact || !source)
51421
+ return null;
51422
+ const record = {
51423
+ callerKey: key,
51424
+ contact,
51425
+ contactId: contact.id,
51426
+ resolvedAt: now(),
51427
+ source,
51428
+ vendor: options.contract.vendor
51429
+ };
51430
+ await Promise.resolve(cache.put(record));
51431
+ return record;
51432
+ };
51433
+ const associate = async (identity, contact) => {
51434
+ const key = cacheKeyFor(identity, options.contract.vendor);
51435
+ const record = {
51436
+ callerKey: key,
51437
+ contact,
51438
+ contactId: contact.id,
51439
+ resolvedAt: now(),
51440
+ source: "manual",
51441
+ vendor: options.contract.vendor
51442
+ };
51443
+ await Promise.resolve(cache.put(record));
51444
+ return record;
51445
+ };
51446
+ const invalidate = async (identity) => {
51447
+ const key = cacheKeyFor(identity, options.contract.vendor);
51448
+ return Promise.resolve(cache.remove(key));
51449
+ };
51450
+ return {
51451
+ associate,
51452
+ contract: options.contract,
51453
+ invalidate,
51454
+ resolve: resolve3
51455
+ };
51456
+ };
51457
+ // src/crmCallLogger.ts
51458
+ var createVoiceCRMCallLogger = (options) => {
51459
+ const now = options.now ?? (() => Date.now());
51460
+ const errorPolicy = options.errorPolicy ?? "swallow";
51461
+ const logCallEnd = async (input) => {
51462
+ const endedAt = input.endedAt ?? now();
51463
+ const duration = input.durationSeconds ?? Math.max(0, Math.round((endedAt - input.startedAt) / 1000));
51464
+ const payload = {
51465
+ durationSeconds: duration,
51466
+ endedAt,
51467
+ sessionId: input.sessionId,
51468
+ startedAt: input.startedAt,
51469
+ ...input.contactId !== undefined ? { contactId: input.contactId } : {},
51470
+ ...input.summary !== undefined ? { summary: input.summary } : {},
51471
+ ...input.disposition !== undefined ? { disposition: input.disposition } : {},
51472
+ ...input.recordingUrl !== undefined ? { recordingUrl: input.recordingUrl } : {},
51473
+ ...input.transcriptUrl !== undefined ? { transcriptUrl: input.transcriptUrl } : {},
51474
+ ...input.metadata !== undefined ? { metadata: input.metadata } : {}
51475
+ };
51476
+ try {
51477
+ const result = await options.contract.logCall(payload);
51478
+ return {
51479
+ activityId: result.activityId,
51480
+ loggedAt: now(),
51481
+ vendor: options.contract.vendor
51482
+ };
51483
+ } catch (rawError) {
51484
+ const error = rawError instanceof Error ? rawError : new Error(String(rawError));
51485
+ await options.onError?.(error, input);
51486
+ if (errorPolicy === "queue") {
51487
+ await options.enqueueOnFailure?.(input);
51488
+ return null;
51489
+ }
51490
+ if (errorPolicy === "throw")
51491
+ throw error;
51492
+ return null;
51493
+ }
51494
+ };
51495
+ const noteOnContact = async (input) => {
51496
+ try {
51497
+ return await options.contract.addNote(input);
51498
+ } catch (rawError) {
51499
+ const error = rawError instanceof Error ? rawError : new Error(String(rawError));
51500
+ await options.onError?.(error, {
51501
+ sessionId: "(note)",
51502
+ startedAt: now(),
51503
+ ...input
51504
+ });
51505
+ if (errorPolicy === "throw")
51506
+ throw error;
51507
+ return null;
51508
+ }
51509
+ };
51510
+ return {
51511
+ contract: options.contract,
51512
+ logCallEnd,
51513
+ noteOnContact
51514
+ };
51515
+ };
51363
51516
  export {
51364
51517
  writeVoiceProofPack,
51365
51518
  writeVoiceMediaPipelineArtifacts,
@@ -52031,6 +52184,7 @@ export {
52031
52184
  createVoiceCampaign,
52032
52185
  createVoiceCallingWindow,
52033
52186
  createVoiceCallerMemoryNamespace,
52187
+ createVoiceCallerCRMLinker,
52034
52188
  createVoiceCallReviewRecorder,
52035
52189
  createVoiceCallReviewFromSession,
52036
52190
  createVoiceCallReviewFromLiveTelephonyReport,
@@ -52038,6 +52192,8 @@ export {
52038
52192
  createVoiceCallDispositionTagger,
52039
52193
  createVoiceCallDebuggerRoutes,
52040
52194
  createVoiceCallCompletedEvent,
52195
+ createVoiceCRMRegistry,
52196
+ createVoiceCRMCallLogger,
52041
52197
  createVoiceCRMActivitySink,
52042
52198
  createVoiceBrowserMediaRoutes,
52043
52199
  createVoiceBrowserCallProfileRoutes,
@@ -52098,6 +52254,7 @@ export {
52098
52254
  createLiveCallViewerFromOptions,
52099
52255
  createLiveCallViewer,
52100
52256
  createJSONVoiceAssistantModel,
52257
+ createInMemoryVoiceCallerCRMLinkCache,
52101
52258
  createInMemoryVoiceCallQuota,
52102
52259
  createInMemoryDNCList,
52103
52260
  createId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.514",
3
+ "version": "0.0.22-beta.515",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",