@absolutejs/voice 0.0.22-beta.113 → 0.0.22-beta.114

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,5 +1,6 @@
1
1
  import { Elysia } from 'elysia';
2
2
  import type { VoiceRedisTaskLeaseCoordinator } from './queue';
3
+ import type { VoiceTelephonyOutcomeDecision, VoiceTelephonyOutcomeProviderEvent, VoiceTelephonyWebhookDecision } from './telephonyOutcome';
3
4
  export type VoiceCampaignStatus = 'canceled' | 'completed' | 'draft' | 'paused' | 'running';
4
5
  export type VoiceCampaignRecipientStatus = 'canceled' | 'completed' | 'failed' | 'pending' | 'queued';
5
6
  export type VoiceCampaignAttemptStatus = 'canceled' | 'failed' | 'queued' | 'running' | 'succeeded';
@@ -251,12 +252,41 @@ export type VoiceCampaignObservabilityReport = {
251
252
  };
252
253
  summary: VoiceCampaignSummary;
253
254
  };
255
+ export type VoiceCampaignTelephonyOutcomeInput<TResult = unknown> = {
256
+ campaignId?: string;
257
+ decision: VoiceTelephonyOutcomeDecision;
258
+ event?: VoiceTelephonyOutcomeProviderEvent;
259
+ externalCallId?: string;
260
+ routeResult?: TResult;
261
+ sessionId?: string;
262
+ attemptId?: string;
263
+ };
264
+ export type VoiceCampaignTelephonyOutcomeStatus = 'failed' | 'ignore' | 'succeeded';
265
+ export type VoiceCampaignTelephonyOutcomeOptions<TResult = unknown> = {
266
+ resolveCampaignId?: (input: VoiceCampaignTelephonyOutcomeInput<TResult>) => Promise<string | undefined> | string | undefined;
267
+ resolveExternalCallId?: (input: VoiceCampaignTelephonyOutcomeInput<TResult>) => Promise<string | undefined> | string | undefined;
268
+ resolveAttemptId?: (input: VoiceCampaignTelephonyOutcomeInput<TResult>) => Promise<string | undefined> | string | undefined;
269
+ runtime?: VoiceCampaignRuntime;
270
+ statusForDecision?: (input: VoiceCampaignTelephonyOutcomeInput<TResult>) => Promise<VoiceCampaignTelephonyOutcomeStatus> | VoiceCampaignTelephonyOutcomeStatus;
271
+ store?: VoiceCampaignStore;
272
+ };
273
+ export type VoiceCampaignTelephonyOutcomeResult = {
274
+ applied: boolean;
275
+ campaignId?: string;
276
+ error?: string;
277
+ externalCallId?: string;
278
+ reason?: 'ignored' | 'missing-attempt' | 'missing-campaign' | 'missing-runtime' | 'terminal-attempt';
279
+ status?: 'failed' | 'succeeded';
280
+ attemptId?: string;
281
+ };
254
282
  export declare const createVoiceMemoryCampaignStore: () => VoiceCampaignStore;
255
283
  export declare const summarizeVoiceCampaigns: (records: VoiceCampaignRecord[]) => VoiceCampaignSummary;
256
284
  export declare const buildVoiceCampaignObservabilityReport: (records: VoiceCampaignRecord[], options?: VoiceCampaignObservabilityOptions) => Promise<VoiceCampaignObservabilityReport>;
257
285
  export declare const createVoiceCampaign: (options: VoiceCampaignRuntimeOptions) => VoiceCampaignRuntime;
258
286
  export declare const createVoiceCampaignWorker: (options: VoiceCampaignWorkerOptions) => VoiceCampaignWorker;
259
287
  export declare const createVoiceCampaignWorkerLoop: (options: VoiceCampaignWorkerLoopOptions) => VoiceCampaignWorkerLoop;
288
+ export declare const applyVoiceCampaignTelephonyOutcome: <TResult = unknown>(input: VoiceCampaignTelephonyOutcomeInput<TResult>, options?: VoiceCampaignTelephonyOutcomeOptions<TResult>) => Promise<VoiceCampaignTelephonyOutcomeResult>;
289
+ export declare const createVoiceCampaignTelephonyOutcomeHandler: <TResult = unknown>(options: VoiceCampaignTelephonyOutcomeOptions<TResult>) => (input: VoiceTelephonyWebhookDecision<TResult>) => Promise<VoiceCampaignTelephonyOutcomeResult>;
260
290
  export declare const runVoiceCampaignProof: (options?: VoiceCampaignProofOptions) => Promise<VoiceCampaignProofReport>;
261
291
  export declare const renderVoiceCampaignsHTML: (records: VoiceCampaignRecord[], options?: {
262
292
  title?: string;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { voice } from './plugin';
2
2
  export { createVoiceAppKit, createVoiceAppKitRoutes, summarizeVoiceAppKitStatus } from './appKit';
3
- export { buildVoiceCampaignObservabilityReport, createVoiceCampaign, createVoiceCampaignRoutes, createVoiceCampaignWorker, createVoiceCampaignWorkerLoop, createVoiceMemoryCampaignStore, renderVoiceCampaignObservabilityHTML, renderVoiceCampaignsHTML, runVoiceCampaignProof, summarizeVoiceCampaigns } from './campaign';
3
+ export { applyVoiceCampaignTelephonyOutcome, buildVoiceCampaignObservabilityReport, createVoiceCampaignTelephonyOutcomeHandler, createVoiceCampaign, createVoiceCampaignRoutes, createVoiceCampaignWorker, createVoiceCampaignWorkerLoop, createVoiceMemoryCampaignStore, renderVoiceCampaignObservabilityHTML, renderVoiceCampaignsHTML, runVoiceCampaignProof, summarizeVoiceCampaigns } from './campaign';
4
4
  export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
5
5
  export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
6
6
  export { createVoiceBargeInRoutes, renderVoiceBargeInHTML, summarizeVoiceBargeIn } from './bargeInRoutes';
package/dist/index.js CHANGED
@@ -10984,6 +10984,155 @@ var createVoiceCampaignWorkerLoop = (options) => {
10984
10984
  tick
10985
10985
  };
10986
10986
  };
10987
+ var firstOutcomeString = (values) => {
10988
+ for (const value of values) {
10989
+ if (typeof value === "string" && value.trim()) {
10990
+ return value.trim();
10991
+ }
10992
+ if (typeof value === "number" && Number.isFinite(value)) {
10993
+ return String(value);
10994
+ }
10995
+ }
10996
+ };
10997
+ var resolveDefaultCampaignOutcomeIds = (input) => {
10998
+ const metadata = input.event?.metadata ?? {};
10999
+ const decisionMetadata = input.decision.metadata ?? {};
11000
+ const routeResult = typeof input.routeResult === "object" && input.routeResult !== null ? input.routeResult : {};
11001
+ return {
11002
+ campaignId: input.campaignId ?? firstOutcomeString([
11003
+ metadata.campaignId,
11004
+ metadata.voiceCampaignId,
11005
+ decisionMetadata.campaignId,
11006
+ decisionMetadata.voiceCampaignId,
11007
+ routeResult.campaignId,
11008
+ routeResult.voiceCampaignId
11009
+ ]),
11010
+ externalCallId: input.externalCallId ?? firstOutcomeString([
11011
+ metadata.externalCallId,
11012
+ metadata.callId,
11013
+ metadata.callSid,
11014
+ metadata.callUuid,
11015
+ decisionMetadata.externalCallId,
11016
+ decisionMetadata.callId,
11017
+ decisionMetadata.callSid,
11018
+ decisionMetadata.callUuid,
11019
+ routeResult.externalCallId,
11020
+ routeResult.callId,
11021
+ routeResult.callSid,
11022
+ routeResult.callUuid,
11023
+ input.sessionId
11024
+ ]),
11025
+ attemptId: input.attemptId ?? firstOutcomeString([
11026
+ metadata.attemptId,
11027
+ metadata.voiceCampaignAttemptId,
11028
+ decisionMetadata.attemptId,
11029
+ decisionMetadata.voiceCampaignAttemptId,
11030
+ routeResult.attemptId,
11031
+ routeResult.voiceCampaignAttemptId
11032
+ ])
11033
+ };
11034
+ };
11035
+ var defaultCampaignOutcomeStatus = (input) => {
11036
+ switch (input.decision.action) {
11037
+ case "complete":
11038
+ case "transfer":
11039
+ return "succeeded";
11040
+ case "escalate":
11041
+ case "no-answer":
11042
+ case "voicemail":
11043
+ return "failed";
11044
+ default:
11045
+ return "ignore";
11046
+ }
11047
+ };
11048
+ var findCampaignAttempt = async (input) => {
11049
+ const records = input.campaignId ? [await input.runtime.get(input.campaignId)].filter(Boolean) : await input.runtime.list();
11050
+ for (const record of records) {
11051
+ const attempt = record.attempts.find((item) => input.attemptId && item.id === input.attemptId || input.externalCallId && item.externalCallId === input.externalCallId);
11052
+ if (attempt) {
11053
+ return {
11054
+ attempt,
11055
+ record
11056
+ };
11057
+ }
11058
+ }
11059
+ };
11060
+ var applyVoiceCampaignTelephonyOutcome = async (input, options = {}) => {
11061
+ const runtime = options.runtime ?? (options.store ? createVoiceCampaign({
11062
+ store: options.store
11063
+ }) : undefined);
11064
+ if (!runtime) {
11065
+ return {
11066
+ applied: false,
11067
+ reason: "missing-runtime"
11068
+ };
11069
+ }
11070
+ const defaults = resolveDefaultCampaignOutcomeIds(input);
11071
+ const campaignId = await options.resolveCampaignId?.(input) ?? defaults.campaignId;
11072
+ const attemptId = await options.resolveAttemptId?.(input) ?? defaults.attemptId;
11073
+ const externalCallId = await options.resolveExternalCallId?.(input) ?? defaults.externalCallId;
11074
+ const status = await options.statusForDecision?.(input) ?? defaultCampaignOutcomeStatus(input);
11075
+ if (status === "ignore") {
11076
+ return {
11077
+ applied: false,
11078
+ campaignId,
11079
+ externalCallId,
11080
+ reason: "ignored",
11081
+ attemptId
11082
+ };
11083
+ }
11084
+ const match = await findCampaignAttempt({
11085
+ attemptId,
11086
+ campaignId,
11087
+ externalCallId,
11088
+ runtime
11089
+ });
11090
+ if (!match) {
11091
+ return {
11092
+ applied: false,
11093
+ campaignId,
11094
+ externalCallId,
11095
+ reason: campaignId ? "missing-attempt" : "missing-campaign",
11096
+ attemptId
11097
+ };
11098
+ }
11099
+ if (match.attempt.status === "failed" || match.attempt.status === "succeeded") {
11100
+ return {
11101
+ applied: false,
11102
+ campaignId: match.record.campaign.id,
11103
+ externalCallId: match.attempt.externalCallId ?? externalCallId,
11104
+ reason: "terminal-attempt",
11105
+ status: match.attempt.status,
11106
+ attemptId: match.attempt.id
11107
+ };
11108
+ }
11109
+ await runtime.completeAttempt(match.record.campaign.id, match.attempt.id, {
11110
+ error: status === "failed" ? input.decision.reason ?? input.decision.disposition ?? input.event?.reason ?? input.event?.status ?? input.decision.action : undefined,
11111
+ externalCallId: externalCallId ?? match.attempt.externalCallId,
11112
+ metadata: {
11113
+ telephonyAction: input.decision.action,
11114
+ telephonyConfidence: input.decision.confidence,
11115
+ telephonyDisposition: input.decision.disposition,
11116
+ telephonyProvider: input.event?.provider,
11117
+ telephonySource: input.decision.source,
11118
+ telephonyStatus: input.event?.status
11119
+ },
11120
+ status
11121
+ });
11122
+ return {
11123
+ applied: true,
11124
+ campaignId: match.record.campaign.id,
11125
+ externalCallId: externalCallId ?? match.attempt.externalCallId,
11126
+ status,
11127
+ attemptId: match.attempt.id
11128
+ };
11129
+ };
11130
+ var createVoiceCampaignTelephonyOutcomeHandler = (options) => (input) => applyVoiceCampaignTelephonyOutcome({
11131
+ decision: input.decision,
11132
+ event: input.event,
11133
+ routeResult: input.routeResult,
11134
+ sessionId: input.sessionId
11135
+ }, options);
10987
11136
  var defaultProofRecipients = () => [
10988
11137
  {
10989
11138
  id: "campaign-proof-recipient-1",
@@ -19330,6 +19479,7 @@ export {
19330
19479
  createVoiceDiagnosticsRoutes,
19331
19480
  createVoiceCampaignWorkerLoop,
19332
19481
  createVoiceCampaignWorker,
19482
+ createVoiceCampaignTelephonyOutcomeHandler,
19333
19483
  createVoiceCampaignRoutes,
19334
19484
  createVoiceCampaign,
19335
19485
  createVoiceCallReviewRecorder,
@@ -19389,6 +19539,7 @@ export {
19389
19539
  applyVoiceOpsTaskPolicy,
19390
19540
  applyVoiceOpsTaskAssignmentRule,
19391
19541
  applyVoiceHandoffDeliveryResult,
19542
+ applyVoiceCampaignTelephonyOutcome,
19392
19543
  applyRiskTieredPhraseHintCorrections,
19393
19544
  applyPhraseHintCorrections,
19394
19545
  TURN_PROFILE_DEFAULTS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.113",
3
+ "version": "0.0.22-beta.114",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",