@absolutejs/voice 0.0.22-beta.30 → 0.0.22-beta.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/handoff.d.ts +15 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +247 -0
- package/dist/queue.d.ts +52 -0
- package/dist/testing/index.js +94 -0
- package/dist/types.d.ts +30 -0
- package/package.json +1 -1
package/dist/handoff.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import type { VoiceHandoffAction, VoiceHandoffAdapter, VoiceHandoffConfig, VoiceHandoffInput, VoiceHandoffResult, VoiceSessionRecord } from './types';
|
|
1
|
+
import type { VoiceHandoffAction, VoiceHandoffAdapter, VoiceHandoffConfig, VoiceHandoffDeliveryStore, VoiceHandoffInput, VoiceHandoffResult, VoiceSessionRecord, StoredVoiceHandoffDelivery } from './types';
|
|
2
2
|
type MaybePromise<T> = T | Promise<T>;
|
|
3
3
|
export type VoiceHandoffDelivery = VoiceHandoffResult & {
|
|
4
4
|
adapterId: string;
|
|
5
5
|
adapterKind?: string;
|
|
6
6
|
};
|
|
7
|
+
export type VoiceHandoffDeliveryRecord<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>;
|
|
7
8
|
export type VoiceHandoffFanoutResult = {
|
|
8
9
|
action: VoiceHandoffAction;
|
|
9
10
|
deliveries: Record<string, VoiceHandoffDelivery>;
|
|
10
11
|
status: VoiceHandoffResult['status'];
|
|
11
12
|
};
|
|
13
|
+
export type VoiceHandoffDeliveryRecordInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Omit<VoiceHandoffInput<TContext, TSession, TResult>, 'api'> & {
|
|
14
|
+
id?: string;
|
|
15
|
+
};
|
|
16
|
+
export type VoiceQueuedHandoffDeliveryOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
17
|
+
adapters: VoiceHandoffAdapter<TContext, TSession, TResult>[];
|
|
18
|
+
api: VoiceHandoffInput<TContext, TSession, TResult>['api'];
|
|
19
|
+
delivery: VoiceHandoffDeliveryRecord<TContext, TSession, TResult>;
|
|
20
|
+
failMode?: VoiceHandoffConfig<TContext, TSession, TResult>['failMode'];
|
|
21
|
+
};
|
|
12
22
|
export type VoiceWebhookHandoffAdapterOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
13
23
|
actions?: VoiceHandoffAction[];
|
|
14
24
|
body?: (input: VoiceHandoffInput<TContext, TSession, TResult>) => MaybePromise<Record<string, unknown>>;
|
|
@@ -35,6 +45,10 @@ export declare const deliverVoiceHandoff: <TContext, TSession extends VoiceSessi
|
|
|
35
45
|
config?: VoiceHandoffConfig<TContext, TSession, TResult>;
|
|
36
46
|
handoff: VoiceHandoffInput<TContext, TSession, TResult>;
|
|
37
47
|
}) => Promise<VoiceHandoffFanoutResult | undefined>;
|
|
48
|
+
export declare const createVoiceHandoffDeliveryRecord: <TContext, TSession extends VoiceSessionRecord, TResult>(input: VoiceHandoffDeliveryRecordInput<TContext, TSession, TResult>) => VoiceHandoffDeliveryRecord<TContext, TSession, TResult>;
|
|
49
|
+
export declare const applyVoiceHandoffDeliveryResult: <TContext, TSession extends VoiceSessionRecord, TResult>(delivery: VoiceHandoffDeliveryRecord<TContext, TSession, TResult>, result: VoiceHandoffFanoutResult) => VoiceHandoffDeliveryRecord<TContext, TSession, TResult>;
|
|
50
|
+
export declare const deliverVoiceHandoffDelivery: <TContext, TSession extends VoiceSessionRecord, TResult>(options: VoiceQueuedHandoffDeliveryOptions<TContext, TSession, TResult>) => Promise<VoiceHandoffDeliveryRecord<TContext, TSession, TResult>>;
|
|
51
|
+
export declare const createVoiceMemoryHandoffDeliveryStore: <TDelivery extends VoiceHandoffDeliveryRecord = VoiceHandoffDeliveryRecord>() => VoiceHandoffDeliveryStore<TDelivery>;
|
|
38
52
|
export declare const createVoiceWebhookHandoffAdapter: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceWebhookHandoffAdapterOptions<TContext, TSession, TResult>) => VoiceHandoffAdapter<TContext, TSession, TResult>;
|
|
39
53
|
export declare const createVoiceTwilioRedirectHandoffAdapter: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceTwilioRedirectHandoffAdapterOptions<TContext, TSession, TResult>) => VoiceHandoffAdapter<TContext, TSession, TResult>;
|
|
40
54
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export { createVoiceS3ReviewStore } from './s3Store';
|
|
|
14
14
|
export { createVoiceMemoryStore } from './memoryStore';
|
|
15
15
|
export { createVoiceCRMActivitySink, createVoiceHelpdeskTicketSink, createVoiceIntegrationHTTPSink, createVoiceHubSpotTaskSink, createVoiceHubSpotTaskSyncSinks, createVoiceHubSpotTaskUpdateSink, createVoiceLinearIssueSink, createVoiceLinearIssueSyncSinks, createVoiceLinearIssueUpdateSink, createVoiceZendeskTicketSink, createVoiceZendeskTicketSyncSinks, createVoiceZendeskTicketUpdateSink, deliverVoiceIntegrationEventToSinks } from './opsSinks';
|
|
16
16
|
export { createVoiceOpsWebhookEnvelope, createVoiceOpsWebhookReceiverRoutes, createVoiceOpsWebhookSink, verifyVoiceOpsWebhookSignature } from './opsWebhook';
|
|
17
|
-
export { createVoiceTwilioRedirectHandoffAdapter, createVoiceWebhookHandoffAdapter, deliverVoiceHandoff } from './handoff';
|
|
17
|
+
export { applyVoiceHandoffDeliveryResult, createVoiceHandoffDeliveryRecord, createVoiceMemoryHandoffDeliveryStore, createVoiceTwilioRedirectHandoffAdapter, createVoiceWebhookHandoffAdapter, deliverVoiceHandoff, deliverVoiceHandoffDelivery } from './handoff';
|
|
18
18
|
export { createVoiceHandoffHealthHTMLHandler, createVoiceHandoffHealthJSONHandler, createVoiceHandoffHealthRoutes, renderVoiceHandoffHealthHTML, summarizeVoiceHandoffHealth } from './handoffHealth';
|
|
19
|
-
export { createVoiceIntegrationSinkWorker, createVoiceIntegrationSinkWorkerLoop, createVoiceOpsTaskWorker, createVoiceOpsTaskProcessorWorker, createVoiceOpsTaskProcessorWorkerLoop, createVoiceRedisIdempotencyStore, createVoiceRedisTaskLeaseCoordinator, createVoiceTraceSinkDeliveryWorker, createVoiceTraceSinkDeliveryWorkerLoop, createVoiceWebhookDeliveryWorker, createVoiceWebhookDeliveryWorkerLoop, summarizeVoiceTraceSinkDeliveries, summarizeVoiceOpsTaskQueue, summarizeVoiceIntegrationEvents } from './queue';
|
|
19
|
+
export { createVoiceHandoffDeliveryWorker, createVoiceHandoffDeliveryWorkerLoop, createVoiceIntegrationSinkWorker, createVoiceIntegrationSinkWorkerLoop, createVoiceOpsTaskWorker, createVoiceOpsTaskProcessorWorker, createVoiceOpsTaskProcessorWorkerLoop, createVoiceRedisIdempotencyStore, createVoiceRedisTaskLeaseCoordinator, createVoiceTraceSinkDeliveryWorker, createVoiceTraceSinkDeliveryWorkerLoop, createVoiceWebhookDeliveryWorker, createVoiceWebhookDeliveryWorkerLoop, summarizeVoiceHandoffDeliveries, summarizeVoiceTraceSinkDeliveries, summarizeVoiceOpsTaskQueue, summarizeVoiceIntegrationEvents } from './queue';
|
|
20
20
|
export { assignVoiceOpsTask, applyVoiceOpsTaskAssignmentRule, applyVoiceOpsTaskPolicy, buildVoiceOpsTaskFromReview, buildVoiceOpsTaskFromSLABreach, claimVoiceOpsTask, completeVoiceOpsTask, createVoiceExternalObjectMap, createVoiceExternalObjectMapId, createVoiceCallCompletedEvent, createVoiceTaskSLABreachedEvent, deadLetterVoiceOpsTask, deliverVoiceIntegrationEvent, failVoiceOpsTask, hasVoiceOpsTaskSLABreach, heartbeatVoiceOpsTask, isVoiceOpsTaskOverdue, markVoiceOpsTaskSLABreached, matchesVoiceOpsTaskAssignmentRule, resolveVoiceOpsTaskAgeBucket, createVoiceIntegrationEvent, createVoiceReviewSavedEvent, resolveVoiceOpsTaskAssignment, resolveVoiceOpsTaskPolicy, requeueVoiceOpsTask, createVoiceTaskCreatedEvent, createVoiceTaskUpdatedEvent, listVoiceOpsTasks, reopenVoiceOpsTask, startVoiceOpsTask, summarizeVoiceOpsTaskAnalytics, summarizeVoiceOpsTasks, withVoiceIntegrationEventId, withVoiceOpsTaskId } from './ops';
|
|
21
21
|
export { createVoiceSession } from './session';
|
|
22
22
|
export { createVoiceCallReviewFromSession, recordVoiceRuntimeOps } from './runtimeOps';
|
|
@@ -42,13 +42,13 @@ export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPrese
|
|
|
42
42
|
export type { VoiceOutcomeRecipe, VoiceOutcomeRecipeName, VoiceOutcomeRecipeOptions } from './outcomeRecipes';
|
|
43
43
|
export type { VoiceCRMActivitySinkOptions, VoiceHubSpotTaskSinkOptions, VoiceHubSpotTaskUpdateSinkOptions, VoiceHelpdeskTicketSinkOptions, VoiceIntegrationHTTPSinkOptions, VoiceIntegrationSink, VoiceIntegrationSinkDeliveryResult, VoiceLinearIssueSinkOptions, VoiceLinearIssueUpdateSinkOptions, VoiceZendeskTicketSinkOptions, VoiceZendeskTicketUpdateSinkOptions } from './opsSinks';
|
|
44
44
|
export type { VoiceOpsWebhookEnvelope, VoiceOpsWebhookEntity, VoiceOpsWebhookLinkResolver, VoiceOpsWebhookReceiverRoutesOptions, VoiceOpsWebhookSinkOptions, VoiceOpsWebhookVerificationResult } from './opsWebhook';
|
|
45
|
-
export type { VoiceHandoffDelivery, VoiceHandoffFanoutResult, VoiceTwilioRedirectHandoffAdapterOptions, VoiceWebhookHandoffAdapterOptions } from './handoff';
|
|
45
|
+
export type { VoiceHandoffDelivery, VoiceHandoffDeliveryRecord, VoiceHandoffDeliveryRecordInput, VoiceHandoffFanoutResult, VoiceQueuedHandoffDeliveryOptions, VoiceTwilioRedirectHandoffAdapterOptions, VoiceWebhookHandoffAdapterOptions } from './handoff';
|
|
46
46
|
export type { VoiceHandoffHealthDelivery, VoiceHandoffHealthEvent, VoiceHandoffHealthHTMLHandlerOptions, VoiceHandoffHealthRoutesOptions, VoiceHandoffHealthStatus, VoiceHandoffHealthSummary, VoiceHandoffHealthSummaryOptions } from './handoffHealth';
|
|
47
47
|
export type { StoredVoiceCallReviewArtifact, VoiceCallReviewArtifact, VoiceCallReviewConfig, VoiceCallReviewPostCallSummary, VoiceCallReviewRecorder, VoiceCallReviewRecorderOptions, VoiceCallReviewStore, VoiceCallReviewSummary, VoiceCallReviewTimelineEvent } from './testing/review';
|
|
48
48
|
export type { VoiceFileRuntimeStorage, VoiceFileStoreOptions } from './fileStore';
|
|
49
49
|
export type { StoredVoiceTraceEvent, VoiceTraceEvaluation, VoiceTraceEvaluationOptions, VoiceTraceEvent, VoiceTraceEventFilter, VoiceTraceEventStore, VoiceTraceEventType, VoiceTraceIssue, VoiceTraceIssueSeverity, VoiceTraceHTTPSinkOptions, VoiceTracePruneFilter, VoiceTracePruneOptions, VoiceTracePruneResult, VoiceTraceRedactionConfig, VoiceTraceRedactionOptions, VoiceTraceRedactionReplacement, VoiceResolvedTraceRedactionOptions, VoiceTraceSink, VoiceTraceSinkDeliveryQueueStatus, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryResult, VoiceTraceSinkDeliveryStatus, VoiceTraceSinkDeliveryStore, VoiceTraceSinkFanoutResult, VoiceTraceSinkStoreOptions, VoiceTraceSummary } from './trace';
|
|
50
50
|
export type { VoicePostgresClient, VoicePostgresRuntimeStorage, VoicePostgresStoreOptions } from './postgresStore';
|
|
51
|
-
export type { VoiceOpsTaskLease, VoiceOpsTaskWorker, VoiceOpsTaskWorkerOptions, VoiceIdempotencyStore, VoiceIntegrationEventQueueSummary, VoiceIntegrationSinkWorkerLoop, VoiceIntegrationSinkWorkerLoopOptions, VoiceIntegrationSinkWorkerOptions, VoiceIntegrationSinkWorkerResult, VoiceRedisIdempotencyClient, VoiceRedisIdempotencyStoreOptions, VoiceRedisTaskLeaseClient, VoiceRedisTaskLeaseCoordinator, VoiceRedisTaskLeaseCoordinatorOptions, VoiceTraceSinkDeliveryQueueSummary, VoiceTraceSinkDeliveryWorkerLoop, VoiceTraceSinkDeliveryWorkerLoopOptions, VoiceTraceSinkDeliveryWorkerOptions, VoiceTraceSinkDeliveryWorkerResult, VoiceOpsTaskClaimFilters, VoiceWebhookDeliveryWorkerLoop, VoiceWebhookDeliveryWorkerLoopOptions, VoiceWebhookDeliveryWorkerOptions, VoiceWebhookDeliveryWorkerResult, VoiceOpsTaskProcessorWorkerLoop, VoiceOpsTaskProcessorWorkerLoopOptions, VoiceOpsTaskProcessorWorkerOptions, VoiceOpsTaskProcessorWorkerResult, VoiceOpsTaskQueueSummary } from './queue';
|
|
51
|
+
export type { VoiceOpsTaskLease, VoiceOpsTaskWorker, VoiceOpsTaskWorkerOptions, VoiceHandoffDeliveryQueueSummary, VoiceHandoffDeliveryWorkerLoop, VoiceHandoffDeliveryWorkerLoopOptions, VoiceHandoffDeliveryWorkerOptions, VoiceHandoffDeliveryWorkerResult, VoiceIdempotencyStore, VoiceIntegrationEventQueueSummary, VoiceIntegrationSinkWorkerLoop, VoiceIntegrationSinkWorkerLoopOptions, VoiceIntegrationSinkWorkerOptions, VoiceIntegrationSinkWorkerResult, VoiceRedisIdempotencyClient, VoiceRedisIdempotencyStoreOptions, VoiceRedisTaskLeaseClient, VoiceRedisTaskLeaseCoordinator, VoiceRedisTaskLeaseCoordinatorOptions, VoiceTraceSinkDeliveryQueueSummary, VoiceTraceSinkDeliveryWorkerLoop, VoiceTraceSinkDeliveryWorkerLoopOptions, VoiceTraceSinkDeliveryWorkerOptions, VoiceTraceSinkDeliveryWorkerResult, VoiceOpsTaskClaimFilters, VoiceWebhookDeliveryWorkerLoop, VoiceWebhookDeliveryWorkerLoopOptions, VoiceWebhookDeliveryWorkerOptions, VoiceWebhookDeliveryWorkerResult, VoiceOpsTaskProcessorWorkerLoop, VoiceOpsTaskProcessorWorkerLoopOptions, VoiceOpsTaskProcessorWorkerOptions, VoiceOpsTaskProcessorWorkerResult, VoiceOpsTaskQueueSummary } from './queue';
|
|
52
52
|
export type { VoiceS3ReviewStoreClient, VoiceS3ReviewStoreFile, VoiceS3ReviewStoreOptions } from './s3Store';
|
|
53
53
|
export type { VoiceSQLiteRuntimeStorage, VoiceSQLiteStoreOptions } from './sqliteStore';
|
|
54
54
|
export type { StoredVoiceIntegrationEvent, StoredVoiceExternalObjectMap, StoredVoiceOpsTask, VoiceExternalObjectMap, VoiceExternalObjectMapStore, VoiceOpsTaskAgeBucket, VoiceOpsTaskAnalyticsOptions, VoiceOpsTaskAnalyticsSummary, VoiceOpsTaskAssignmentRule, VoiceOpsTaskAssignmentRuleCondition, VoiceOpsTaskAssignmentRules, VoiceOpsTaskAssigneeAnalytics, VoiceOpsDispositionTaskPolicies, VoiceOpsSLABreachPolicy, VoiceIntegrationDeliveryStatus, VoiceIntegrationEvent, VoiceIntegrationEventStore, VoiceIntegrationSinkDelivery, VoiceIntegrationEventType, VoiceIntegrationWebhookConfig, VoiceOpsTask, VoiceOpsTaskHistoryEntry, VoiceOpsTaskKind, VoiceOpsTaskPolicy, VoiceOpsTaskPriority, VoiceOpsTaskStatus, VoiceOpsTaskStore, VoiceOpsTaskSummary, VoiceOpsTaskWorkerAnalytics } from './ops';
|
package/dist/index.js
CHANGED
|
@@ -3019,6 +3019,14 @@ var aggregateHandoffStatus = (deliveries) => {
|
|
|
3019
3019
|
}
|
|
3020
3020
|
return "skipped";
|
|
3021
3021
|
};
|
|
3022
|
+
var createHandoffDeliveryId = (input) => [
|
|
3023
|
+
"voice-handoff",
|
|
3024
|
+
input.sessionId,
|
|
3025
|
+
input.action,
|
|
3026
|
+
Date.now(),
|
|
3027
|
+
crypto.randomUUID()
|
|
3028
|
+
].join(":");
|
|
3029
|
+
var resolveHandoffDeliveryError = (deliveries) => Object.values(deliveries).map((delivery) => delivery.error).find(Boolean);
|
|
3022
3030
|
var defaultWebhookBody = (input) => ({
|
|
3023
3031
|
action: input.action,
|
|
3024
3032
|
metadata: input.metadata,
|
|
@@ -3067,6 +3075,73 @@ var deliverVoiceHandoff = async (input) => {
|
|
|
3067
3075
|
status: aggregateHandoffStatus(deliveries)
|
|
3068
3076
|
};
|
|
3069
3077
|
};
|
|
3078
|
+
var createVoiceHandoffDeliveryRecord = (input) => {
|
|
3079
|
+
const now = Date.now();
|
|
3080
|
+
return {
|
|
3081
|
+
action: input.action,
|
|
3082
|
+
context: input.context,
|
|
3083
|
+
createdAt: now,
|
|
3084
|
+
deliveryAttempts: 0,
|
|
3085
|
+
deliveryStatus: "pending",
|
|
3086
|
+
id: input.id ?? createHandoffDeliveryId({
|
|
3087
|
+
action: input.action,
|
|
3088
|
+
sessionId: input.session.id
|
|
3089
|
+
}),
|
|
3090
|
+
metadata: input.metadata,
|
|
3091
|
+
reason: input.reason,
|
|
3092
|
+
result: input.result,
|
|
3093
|
+
session: input.session,
|
|
3094
|
+
sessionId: input.session.id,
|
|
3095
|
+
target: input.target,
|
|
3096
|
+
updatedAt: now
|
|
3097
|
+
};
|
|
3098
|
+
};
|
|
3099
|
+
var applyVoiceHandoffDeliveryResult = (delivery, result) => ({
|
|
3100
|
+
...delivery,
|
|
3101
|
+
deliveredAt: result.status === "delivered" || result.status === "skipped" ? Date.now() : delivery.deliveredAt,
|
|
3102
|
+
deliveries: result.deliveries,
|
|
3103
|
+
deliveryAttempts: (delivery.deliveryAttempts ?? 0) + 1,
|
|
3104
|
+
deliveryError: result.status === "failed" ? resolveHandoffDeliveryError(result.deliveries) : undefined,
|
|
3105
|
+
deliveryStatus: result.status,
|
|
3106
|
+
updatedAt: Date.now()
|
|
3107
|
+
});
|
|
3108
|
+
var deliverVoiceHandoffDelivery = async (options) => {
|
|
3109
|
+
const result = await deliverVoiceHandoff({
|
|
3110
|
+
config: {
|
|
3111
|
+
adapters: options.adapters,
|
|
3112
|
+
failMode: options.failMode
|
|
3113
|
+
},
|
|
3114
|
+
handoff: {
|
|
3115
|
+
action: options.delivery.action,
|
|
3116
|
+
api: options.api,
|
|
3117
|
+
context: options.delivery.context,
|
|
3118
|
+
metadata: options.delivery.metadata,
|
|
3119
|
+
reason: options.delivery.reason,
|
|
3120
|
+
result: options.delivery.result,
|
|
3121
|
+
session: options.delivery.session,
|
|
3122
|
+
target: options.delivery.target
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
return result ? applyVoiceHandoffDeliveryResult(options.delivery, result) : {
|
|
3126
|
+
...options.delivery,
|
|
3127
|
+
deliveryAttempts: (options.delivery.deliveryAttempts ?? 0) + 1,
|
|
3128
|
+
deliveryStatus: "skipped",
|
|
3129
|
+
updatedAt: Date.now()
|
|
3130
|
+
};
|
|
3131
|
+
};
|
|
3132
|
+
var createVoiceMemoryHandoffDeliveryStore = () => {
|
|
3133
|
+
const deliveries = new Map;
|
|
3134
|
+
return {
|
|
3135
|
+
get: async (id) => deliveries.get(id),
|
|
3136
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
3137
|
+
remove: async (id) => {
|
|
3138
|
+
deliveries.delete(id);
|
|
3139
|
+
},
|
|
3140
|
+
set: async (id, delivery) => {
|
|
3141
|
+
deliveries.set(id, delivery);
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
};
|
|
3070
3145
|
var createVoiceWebhookHandoffAdapter = (options) => ({
|
|
3071
3146
|
actions: options.actions,
|
|
3072
3147
|
handoff: async (input) => {
|
|
@@ -3609,6 +3684,21 @@ var createVoiceSession = (options) => {
|
|
|
3609
3684
|
});
|
|
3610
3685
|
};
|
|
3611
3686
|
const runHandoff = async (input) => {
|
|
3687
|
+
const queuedDelivery = options.handoff?.deliveryQueue ? createVoiceHandoffDeliveryRecord({
|
|
3688
|
+
action: input.action,
|
|
3689
|
+
context: options.context,
|
|
3690
|
+
metadata: input.metadata,
|
|
3691
|
+
reason: input.reason,
|
|
3692
|
+
result: input.result,
|
|
3693
|
+
session: input.session,
|
|
3694
|
+
target: input.target
|
|
3695
|
+
}) : undefined;
|
|
3696
|
+
if (queuedDelivery) {
|
|
3697
|
+
await options.handoff?.deliveryQueue?.set(queuedDelivery.id, queuedDelivery);
|
|
3698
|
+
}
|
|
3699
|
+
if (options.handoff?.enqueueOnly) {
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3612
3702
|
const result = await deliverVoiceHandoff({
|
|
3613
3703
|
config: options.handoff,
|
|
3614
3704
|
handoff: {
|
|
@@ -3625,6 +3715,10 @@ var createVoiceSession = (options) => {
|
|
|
3625
3715
|
if (!result) {
|
|
3626
3716
|
return;
|
|
3627
3717
|
}
|
|
3718
|
+
if (queuedDelivery) {
|
|
3719
|
+
const updatedDelivery = applyVoiceHandoffDeliveryResult(queuedDelivery, result);
|
|
3720
|
+
await options.handoff?.deliveryQueue?.set(updatedDelivery.id, updatedDelivery);
|
|
3721
|
+
}
|
|
3628
3722
|
await appendTrace({
|
|
3629
3723
|
metadata: input.metadata,
|
|
3630
3724
|
payload: {
|
|
@@ -9625,6 +9719,8 @@ var shouldDeadLetterSinkEvent = (event, sinks, maxFailures) => typeof maxFailure
|
|
|
9625
9719
|
var shouldDeadLetterTask = (task, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (task.processingAttempts ?? 0) >= maxFailures;
|
|
9626
9720
|
var shouldProcessTraceDeliveryStatus = (status, allowed) => allowed.includes(status);
|
|
9627
9721
|
var shouldDeadLetterTraceDelivery = (delivery, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (delivery.deliveryAttempts ?? 0) >= maxFailures;
|
|
9722
|
+
var shouldProcessHandoffDeliveryStatus = (status, allowed) => allowed.includes(status);
|
|
9723
|
+
var shouldDeadLetterHandoffDelivery = (delivery, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (delivery.deliveryAttempts ?? 0) >= maxFailures;
|
|
9628
9724
|
var summarizeVoiceIntegrationEvents = (events, input = {}) => {
|
|
9629
9725
|
const buildSummary = async () => {
|
|
9630
9726
|
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((event) => event.id) : []);
|
|
@@ -9706,6 +9802,48 @@ var summarizeVoiceTraceSinkDeliveries = (deliveries, input = {}) => {
|
|
|
9706
9802
|
};
|
|
9707
9803
|
return buildSummary();
|
|
9708
9804
|
};
|
|
9805
|
+
var summarizeVoiceHandoffDeliveries = (deliveries, input = {}) => {
|
|
9806
|
+
const buildSummary = async () => {
|
|
9807
|
+
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((delivery) => delivery.id) : []);
|
|
9808
|
+
const byAction = new Map;
|
|
9809
|
+
const summary = {
|
|
9810
|
+
byAction: [],
|
|
9811
|
+
deadLettered: 0,
|
|
9812
|
+
delivered: 0,
|
|
9813
|
+
failed: 0,
|
|
9814
|
+
pending: 0,
|
|
9815
|
+
retryEligible: 0,
|
|
9816
|
+
skipped: 0,
|
|
9817
|
+
total: deliveries.length
|
|
9818
|
+
};
|
|
9819
|
+
for (const delivery of deliveries) {
|
|
9820
|
+
byAction.set(delivery.action, (byAction.get(delivery.action) ?? 0) + 1);
|
|
9821
|
+
if (deadLetterIds.has(delivery.id)) {
|
|
9822
|
+
summary.deadLettered += 1;
|
|
9823
|
+
}
|
|
9824
|
+
switch (delivery.deliveryStatus) {
|
|
9825
|
+
case "delivered":
|
|
9826
|
+
summary.delivered += 1;
|
|
9827
|
+
break;
|
|
9828
|
+
case "failed":
|
|
9829
|
+
summary.failed += 1;
|
|
9830
|
+
if ((delivery.deliveryAttempts ?? 0) > 0) {
|
|
9831
|
+
summary.retryEligible += 1;
|
|
9832
|
+
}
|
|
9833
|
+
break;
|
|
9834
|
+
case "skipped":
|
|
9835
|
+
summary.skipped += 1;
|
|
9836
|
+
break;
|
|
9837
|
+
case "pending":
|
|
9838
|
+
summary.pending += 1;
|
|
9839
|
+
break;
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
summary.byAction = [...byAction.entries()].sort((left, right) => right[1] - left[1]);
|
|
9843
|
+
return summary;
|
|
9844
|
+
};
|
|
9845
|
+
return buildSummary();
|
|
9846
|
+
};
|
|
9709
9847
|
var summarizeVoiceOpsTaskQueue = (tasks, input = {}) => {
|
|
9710
9848
|
const buildSummary = async () => {
|
|
9711
9849
|
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((task) => task.id) : []);
|
|
@@ -10135,6 +10273,108 @@ var createVoiceTraceSinkDeliveryWorkerLoop = (options) => {
|
|
|
10135
10273
|
tick
|
|
10136
10274
|
};
|
|
10137
10275
|
};
|
|
10276
|
+
var createVoiceHandoffDeliveryWorker = (options) => {
|
|
10277
|
+
const allowedStatuses = options.statuses ?? ["pending", "failed"];
|
|
10278
|
+
const leaseMs = Math.max(1, options.leaseMs ?? 30000);
|
|
10279
|
+
return {
|
|
10280
|
+
drain: async () => {
|
|
10281
|
+
const result = {
|
|
10282
|
+
alreadyProcessed: 0,
|
|
10283
|
+
attempted: 0,
|
|
10284
|
+
deadLettered: 0,
|
|
10285
|
+
delivered: 0,
|
|
10286
|
+
failed: 0,
|
|
10287
|
+
skipped: 0
|
|
10288
|
+
};
|
|
10289
|
+
const deliveries = [...await options.deliveries.list()].sort((left, right) => left.createdAt - right.createdAt);
|
|
10290
|
+
for (const delivery of deliveries) {
|
|
10291
|
+
if (!shouldProcessHandoffDeliveryStatus(delivery.deliveryStatus, allowedStatuses)) {
|
|
10292
|
+
continue;
|
|
10293
|
+
}
|
|
10294
|
+
if (shouldDeadLetterHandoffDelivery(delivery, options.maxFailures)) {
|
|
10295
|
+
await options.deadLetters?.set(delivery.id, delivery);
|
|
10296
|
+
await options.onDeadLetter?.(delivery);
|
|
10297
|
+
result.deadLettered += 1;
|
|
10298
|
+
continue;
|
|
10299
|
+
}
|
|
10300
|
+
const claimed = await options.leases.claim({
|
|
10301
|
+
leaseMs,
|
|
10302
|
+
taskId: delivery.id,
|
|
10303
|
+
workerId: options.workerId
|
|
10304
|
+
});
|
|
10305
|
+
if (!claimed) {
|
|
10306
|
+
continue;
|
|
10307
|
+
}
|
|
10308
|
+
try {
|
|
10309
|
+
const idempotencyKey = `${delivery.id}:handoff`;
|
|
10310
|
+
if (options.idempotency && await options.idempotency.has(idempotencyKey)) {
|
|
10311
|
+
result.alreadyProcessed += 1;
|
|
10312
|
+
continue;
|
|
10313
|
+
}
|
|
10314
|
+
result.attempted += 1;
|
|
10315
|
+
const updatedDelivery = await deliverVoiceHandoffDelivery({
|
|
10316
|
+
adapters: options.adapters,
|
|
10317
|
+
api: options.api,
|
|
10318
|
+
delivery,
|
|
10319
|
+
failMode: options.failMode
|
|
10320
|
+
});
|
|
10321
|
+
await options.deliveries.set(updatedDelivery.id, updatedDelivery);
|
|
10322
|
+
if (updatedDelivery.deliveryStatus === "delivered" || updatedDelivery.deliveryStatus === "skipped") {
|
|
10323
|
+
await options.idempotency?.set(idempotencyKey, {
|
|
10324
|
+
ttlSeconds: options.idempotencyTtlSeconds
|
|
10325
|
+
});
|
|
10326
|
+
}
|
|
10327
|
+
if (updatedDelivery.deliveryStatus === "delivered") {
|
|
10328
|
+
result.delivered += 1;
|
|
10329
|
+
} else if (updatedDelivery.deliveryStatus === "skipped") {
|
|
10330
|
+
result.skipped += 1;
|
|
10331
|
+
} else if (updatedDelivery.deliveryStatus === "failed") {
|
|
10332
|
+
result.failed += 1;
|
|
10333
|
+
if (shouldDeadLetterHandoffDelivery(updatedDelivery, options.maxFailures)) {
|
|
10334
|
+
await options.deadLetters?.set(updatedDelivery.id, updatedDelivery);
|
|
10335
|
+
await options.onDeadLetter?.(updatedDelivery);
|
|
10336
|
+
result.deadLettered += 1;
|
|
10337
|
+
}
|
|
10338
|
+
}
|
|
10339
|
+
} finally {
|
|
10340
|
+
await options.leases.release({
|
|
10341
|
+
taskId: delivery.id,
|
|
10342
|
+
workerId: options.workerId
|
|
10343
|
+
});
|
|
10344
|
+
}
|
|
10345
|
+
}
|
|
10346
|
+
return result;
|
|
10347
|
+
}
|
|
10348
|
+
};
|
|
10349
|
+
};
|
|
10350
|
+
var createVoiceHandoffDeliveryWorkerLoop = (options) => {
|
|
10351
|
+
const pollIntervalMs = Math.max(1, options.pollIntervalMs ?? 1000);
|
|
10352
|
+
let timer;
|
|
10353
|
+
let running = false;
|
|
10354
|
+
const tick = async () => options.worker.drain();
|
|
10355
|
+
return {
|
|
10356
|
+
isRunning: () => running,
|
|
10357
|
+
start: () => {
|
|
10358
|
+
if (timer) {
|
|
10359
|
+
return;
|
|
10360
|
+
}
|
|
10361
|
+
running = true;
|
|
10362
|
+
timer = setInterval(() => {
|
|
10363
|
+
tick().catch((error) => {
|
|
10364
|
+
options.onError?.(error);
|
|
10365
|
+
});
|
|
10366
|
+
}, pollIntervalMs);
|
|
10367
|
+
},
|
|
10368
|
+
stop: () => {
|
|
10369
|
+
if (timer) {
|
|
10370
|
+
clearInterval(timer);
|
|
10371
|
+
timer = undefined;
|
|
10372
|
+
}
|
|
10373
|
+
running = false;
|
|
10374
|
+
},
|
|
10375
|
+
tick
|
|
10376
|
+
};
|
|
10377
|
+
};
|
|
10138
10378
|
var createVoiceOpsTaskWorker = (options) => {
|
|
10139
10379
|
const leaseMs = Math.max(1, options.leaseMs ?? 30000);
|
|
10140
10380
|
const getTask = async (taskId) => {
|
|
@@ -11532,6 +11772,7 @@ export {
|
|
|
11532
11772
|
summarizeVoiceOpsTaskAnalytics,
|
|
11533
11773
|
summarizeVoiceIntegrationEvents,
|
|
11534
11774
|
summarizeVoiceHandoffHealth,
|
|
11775
|
+
summarizeVoiceHandoffDeliveries,
|
|
11535
11776
|
summarizeVoiceAssistantRuns,
|
|
11536
11777
|
summarizeVoiceAssistantHealth,
|
|
11537
11778
|
startVoiceOpsTask,
|
|
@@ -11577,6 +11818,7 @@ export {
|
|
|
11577
11818
|
deliverVoiceTraceEventsToSinks,
|
|
11578
11819
|
deliverVoiceIntegrationEventToSinks,
|
|
11579
11820
|
deliverVoiceIntegrationEvent,
|
|
11821
|
+
deliverVoiceHandoffDelivery,
|
|
11580
11822
|
deliverVoiceHandoff,
|
|
11581
11823
|
decodeTwilioMulawBase64,
|
|
11582
11824
|
deadLetterVoiceOpsTask,
|
|
@@ -11641,6 +11883,7 @@ export {
|
|
|
11641
11883
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
11642
11884
|
createVoiceMemoryTraceEventStore,
|
|
11643
11885
|
createVoiceMemoryStore,
|
|
11886
|
+
createVoiceMemoryHandoffDeliveryStore,
|
|
11644
11887
|
createVoiceMemoryAssistantMemoryStore,
|
|
11645
11888
|
createVoiceLinearIssueUpdateSink,
|
|
11646
11889
|
createVoiceLinearIssueSyncSinks,
|
|
@@ -11656,6 +11899,9 @@ export {
|
|
|
11656
11899
|
createVoiceHandoffHealthRoutes,
|
|
11657
11900
|
createVoiceHandoffHealthJSONHandler,
|
|
11658
11901
|
createVoiceHandoffHealthHTMLHandler,
|
|
11902
|
+
createVoiceHandoffDeliveryWorkerLoop,
|
|
11903
|
+
createVoiceHandoffDeliveryWorker,
|
|
11904
|
+
createVoiceHandoffDeliveryRecord,
|
|
11659
11905
|
createVoiceFileTraceSinkDeliveryStore,
|
|
11660
11906
|
createVoiceFileTraceEventStore,
|
|
11661
11907
|
createVoiceFileTaskStore,
|
|
@@ -11706,6 +11952,7 @@ export {
|
|
|
11706
11952
|
assignVoiceOpsTask,
|
|
11707
11953
|
applyVoiceOpsTaskPolicy,
|
|
11708
11954
|
applyVoiceOpsTaskAssignmentRule,
|
|
11955
|
+
applyVoiceHandoffDeliveryResult,
|
|
11709
11956
|
applyRiskTieredPhraseHintCorrections,
|
|
11710
11957
|
applyPhraseHintCorrections,
|
|
11711
11958
|
TURN_PROFILE_DEFAULTS
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RedisClient } from 'bun';
|
|
2
2
|
import type { VoiceIntegrationSink } from './opsSinks';
|
|
3
3
|
import type { VoiceTraceRedactionConfig, VoiceTraceSink, VoiceTraceSinkDeliveryRecord, VoiceTraceSinkDeliveryStore, VoiceTraceSinkDeliveryQueueStatus } from './trace';
|
|
4
|
+
import type { StoredVoiceHandoffDelivery, VoiceHandoffAdapter, VoiceHandoffDeliveryQueueStatus, VoiceHandoffDeliveryStore, VoiceSessionHandle, VoiceSessionRecord } from './types';
|
|
4
5
|
import type { VoiceOpsTaskPriority, StoredVoiceOpsTask, StoredVoiceIntegrationEvent, VoiceIntegrationDeliveryStatus, VoiceIntegrationEventStore, VoiceIntegrationWebhookConfig, VoiceOpsTaskKind, VoiceOpsTaskStatus, VoiceOpsTaskStore } from './ops';
|
|
5
6
|
export type VoiceOpsTaskLease = {
|
|
6
7
|
expiresAt: number;
|
|
@@ -159,6 +160,50 @@ export type VoiceTraceSinkDeliveryQueueSummary = {
|
|
|
159
160
|
skipped: number;
|
|
160
161
|
total: number;
|
|
161
162
|
};
|
|
163
|
+
export type VoiceHandoffDeliveryWorkerOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>> = {
|
|
164
|
+
adapters: VoiceHandoffAdapter<TContext, TSession, TResult>[];
|
|
165
|
+
api: VoiceSessionHandle<TContext, TSession, TResult>;
|
|
166
|
+
deadLetters?: VoiceHandoffDeliveryStore<TDelivery>;
|
|
167
|
+
deliveries: VoiceHandoffDeliveryStore<TDelivery>;
|
|
168
|
+
failMode?: 'record' | 'throw';
|
|
169
|
+
idempotency?: VoiceIdempotencyStore;
|
|
170
|
+
idempotencyTtlSeconds?: number;
|
|
171
|
+
leaseMs?: number;
|
|
172
|
+
leases: VoiceRedisTaskLeaseCoordinator;
|
|
173
|
+
maxFailures?: number;
|
|
174
|
+
onDeadLetter?: (delivery: TDelivery) => Promise<void> | void;
|
|
175
|
+
statuses?: VoiceHandoffDeliveryQueueStatus[];
|
|
176
|
+
workerId: string;
|
|
177
|
+
};
|
|
178
|
+
export type VoiceHandoffDeliveryWorkerResult = {
|
|
179
|
+
alreadyProcessed: number;
|
|
180
|
+
attempted: number;
|
|
181
|
+
deadLettered: number;
|
|
182
|
+
delivered: number;
|
|
183
|
+
failed: number;
|
|
184
|
+
skipped: number;
|
|
185
|
+
};
|
|
186
|
+
export type VoiceHandoffDeliveryWorkerLoopOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>> = {
|
|
187
|
+
onError?: (error: unknown) => Promise<void> | void;
|
|
188
|
+
pollIntervalMs?: number;
|
|
189
|
+
worker: ReturnType<typeof createVoiceHandoffDeliveryWorker<TContext, TSession, TResult, TDelivery>>;
|
|
190
|
+
};
|
|
191
|
+
export type VoiceHandoffDeliveryWorkerLoop = {
|
|
192
|
+
isRunning: () => boolean;
|
|
193
|
+
start: () => void;
|
|
194
|
+
stop: () => void;
|
|
195
|
+
tick: () => Promise<VoiceHandoffDeliveryWorkerResult>;
|
|
196
|
+
};
|
|
197
|
+
export type VoiceHandoffDeliveryQueueSummary = {
|
|
198
|
+
byAction: Array<[StoredVoiceHandoffDelivery['action'], number]>;
|
|
199
|
+
deadLettered: number;
|
|
200
|
+
delivered: number;
|
|
201
|
+
failed: number;
|
|
202
|
+
pending: number;
|
|
203
|
+
retryEligible: number;
|
|
204
|
+
skipped: number;
|
|
205
|
+
total: number;
|
|
206
|
+
};
|
|
162
207
|
export type VoiceOpsTaskWorkerOptions<TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask> = {
|
|
163
208
|
leaseMs?: number;
|
|
164
209
|
leases: VoiceRedisTaskLeaseCoordinator;
|
|
@@ -252,6 +297,9 @@ export declare const summarizeVoiceIntegrationEvents: <TEvent extends StoredVoic
|
|
|
252
297
|
export declare const summarizeVoiceTraceSinkDeliveries: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(deliveries: TDelivery[], input?: {
|
|
253
298
|
deadLetters?: VoiceTraceSinkDeliveryStore<TDelivery>;
|
|
254
299
|
}) => Promise<VoiceTraceSinkDeliveryQueueSummary> | VoiceTraceSinkDeliveryQueueSummary;
|
|
300
|
+
export declare const summarizeVoiceHandoffDeliveries: <TDelivery extends StoredVoiceHandoffDelivery = StoredVoiceHandoffDelivery>(deliveries: TDelivery[], input?: {
|
|
301
|
+
deadLetters?: VoiceHandoffDeliveryStore<TDelivery>;
|
|
302
|
+
}) => Promise<VoiceHandoffDeliveryQueueSummary> | VoiceHandoffDeliveryQueueSummary;
|
|
255
303
|
export declare const summarizeVoiceOpsTaskQueue: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(tasks: TTask[], input?: {
|
|
256
304
|
deadLetters?: VoiceOpsTaskStore<TTask>;
|
|
257
305
|
}) => Promise<VoiceOpsTaskQueueSummary> | VoiceOpsTaskQueueSummary;
|
|
@@ -269,6 +317,10 @@ export declare const createVoiceTraceSinkDeliveryWorker: <TDelivery extends Voic
|
|
|
269
317
|
drain: () => Promise<VoiceTraceSinkDeliveryWorkerResult>;
|
|
270
318
|
};
|
|
271
319
|
export declare const createVoiceTraceSinkDeliveryWorkerLoop: <TDelivery extends VoiceTraceSinkDeliveryRecord = VoiceTraceSinkDeliveryRecord>(options: VoiceTraceSinkDeliveryWorkerLoopOptions<TDelivery>) => VoiceTraceSinkDeliveryWorkerLoop;
|
|
320
|
+
export declare const createVoiceHandoffDeliveryWorker: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>>(options: VoiceHandoffDeliveryWorkerOptions<TContext, TSession, TResult, TDelivery>) => {
|
|
321
|
+
drain: () => Promise<VoiceHandoffDeliveryWorkerResult>;
|
|
322
|
+
};
|
|
323
|
+
export declare const createVoiceHandoffDeliveryWorkerLoop: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown, TDelivery extends StoredVoiceHandoffDelivery<TContext, TSession, TResult> = StoredVoiceHandoffDelivery<TContext, TSession, TResult>>(options: VoiceHandoffDeliveryWorkerLoopOptions<TContext, TSession, TResult, TDelivery>) => VoiceHandoffDeliveryWorkerLoop;
|
|
272
324
|
export declare const createVoiceOpsTaskWorker: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(options: VoiceOpsTaskWorkerOptions<TTask>) => VoiceOpsTaskWorker<TTask>;
|
|
273
325
|
export declare const createVoiceOpsTaskProcessorWorker: <TTask extends StoredVoiceOpsTask = StoredVoiceOpsTask>(options: VoiceOpsTaskProcessorWorkerOptions<TTask>) => {
|
|
274
326
|
drain: () => Promise<VoiceOpsTaskProcessorWorkerResult>;
|
package/dist/testing/index.js
CHANGED
|
@@ -4486,6 +4486,14 @@ var aggregateHandoffStatus = (deliveries) => {
|
|
|
4486
4486
|
}
|
|
4487
4487
|
return "skipped";
|
|
4488
4488
|
};
|
|
4489
|
+
var createHandoffDeliveryId = (input) => [
|
|
4490
|
+
"voice-handoff",
|
|
4491
|
+
input.sessionId,
|
|
4492
|
+
input.action,
|
|
4493
|
+
Date.now(),
|
|
4494
|
+
crypto.randomUUID()
|
|
4495
|
+
].join(":");
|
|
4496
|
+
var resolveHandoffDeliveryError = (deliveries) => Object.values(deliveries).map((delivery) => delivery.error).find(Boolean);
|
|
4489
4497
|
var defaultWebhookBody = (input) => ({
|
|
4490
4498
|
action: input.action,
|
|
4491
4499
|
metadata: input.metadata,
|
|
@@ -4534,6 +4542,73 @@ var deliverVoiceHandoff = async (input) => {
|
|
|
4534
4542
|
status: aggregateHandoffStatus(deliveries)
|
|
4535
4543
|
};
|
|
4536
4544
|
};
|
|
4545
|
+
var createVoiceHandoffDeliveryRecord = (input) => {
|
|
4546
|
+
const now = Date.now();
|
|
4547
|
+
return {
|
|
4548
|
+
action: input.action,
|
|
4549
|
+
context: input.context,
|
|
4550
|
+
createdAt: now,
|
|
4551
|
+
deliveryAttempts: 0,
|
|
4552
|
+
deliveryStatus: "pending",
|
|
4553
|
+
id: input.id ?? createHandoffDeliveryId({
|
|
4554
|
+
action: input.action,
|
|
4555
|
+
sessionId: input.session.id
|
|
4556
|
+
}),
|
|
4557
|
+
metadata: input.metadata,
|
|
4558
|
+
reason: input.reason,
|
|
4559
|
+
result: input.result,
|
|
4560
|
+
session: input.session,
|
|
4561
|
+
sessionId: input.session.id,
|
|
4562
|
+
target: input.target,
|
|
4563
|
+
updatedAt: now
|
|
4564
|
+
};
|
|
4565
|
+
};
|
|
4566
|
+
var applyVoiceHandoffDeliveryResult = (delivery, result) => ({
|
|
4567
|
+
...delivery,
|
|
4568
|
+
deliveredAt: result.status === "delivered" || result.status === "skipped" ? Date.now() : delivery.deliveredAt,
|
|
4569
|
+
deliveries: result.deliveries,
|
|
4570
|
+
deliveryAttempts: (delivery.deliveryAttempts ?? 0) + 1,
|
|
4571
|
+
deliveryError: result.status === "failed" ? resolveHandoffDeliveryError(result.deliveries) : undefined,
|
|
4572
|
+
deliveryStatus: result.status,
|
|
4573
|
+
updatedAt: Date.now()
|
|
4574
|
+
});
|
|
4575
|
+
var deliverVoiceHandoffDelivery = async (options) => {
|
|
4576
|
+
const result = await deliverVoiceHandoff({
|
|
4577
|
+
config: {
|
|
4578
|
+
adapters: options.adapters,
|
|
4579
|
+
failMode: options.failMode
|
|
4580
|
+
},
|
|
4581
|
+
handoff: {
|
|
4582
|
+
action: options.delivery.action,
|
|
4583
|
+
api: options.api,
|
|
4584
|
+
context: options.delivery.context,
|
|
4585
|
+
metadata: options.delivery.metadata,
|
|
4586
|
+
reason: options.delivery.reason,
|
|
4587
|
+
result: options.delivery.result,
|
|
4588
|
+
session: options.delivery.session,
|
|
4589
|
+
target: options.delivery.target
|
|
4590
|
+
}
|
|
4591
|
+
});
|
|
4592
|
+
return result ? applyVoiceHandoffDeliveryResult(options.delivery, result) : {
|
|
4593
|
+
...options.delivery,
|
|
4594
|
+
deliveryAttempts: (options.delivery.deliveryAttempts ?? 0) + 1,
|
|
4595
|
+
deliveryStatus: "skipped",
|
|
4596
|
+
updatedAt: Date.now()
|
|
4597
|
+
};
|
|
4598
|
+
};
|
|
4599
|
+
var createVoiceMemoryHandoffDeliveryStore = () => {
|
|
4600
|
+
const deliveries = new Map;
|
|
4601
|
+
return {
|
|
4602
|
+
get: async (id) => deliveries.get(id),
|
|
4603
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
4604
|
+
remove: async (id) => {
|
|
4605
|
+
deliveries.delete(id);
|
|
4606
|
+
},
|
|
4607
|
+
set: async (id, delivery) => {
|
|
4608
|
+
deliveries.set(id, delivery);
|
|
4609
|
+
}
|
|
4610
|
+
};
|
|
4611
|
+
};
|
|
4537
4612
|
var createVoiceWebhookHandoffAdapter = (options) => ({
|
|
4538
4613
|
actions: options.actions,
|
|
4539
4614
|
handoff: async (input) => {
|
|
@@ -4972,6 +5047,21 @@ var createVoiceSession = (options) => {
|
|
|
4972
5047
|
});
|
|
4973
5048
|
};
|
|
4974
5049
|
const runHandoff = async (input) => {
|
|
5050
|
+
const queuedDelivery = options.handoff?.deliveryQueue ? createVoiceHandoffDeliveryRecord({
|
|
5051
|
+
action: input.action,
|
|
5052
|
+
context: options.context,
|
|
5053
|
+
metadata: input.metadata,
|
|
5054
|
+
reason: input.reason,
|
|
5055
|
+
result: input.result,
|
|
5056
|
+
session: input.session,
|
|
5057
|
+
target: input.target
|
|
5058
|
+
}) : undefined;
|
|
5059
|
+
if (queuedDelivery) {
|
|
5060
|
+
await options.handoff?.deliveryQueue?.set(queuedDelivery.id, queuedDelivery);
|
|
5061
|
+
}
|
|
5062
|
+
if (options.handoff?.enqueueOnly) {
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
4975
5065
|
const result = await deliverVoiceHandoff({
|
|
4976
5066
|
config: options.handoff,
|
|
4977
5067
|
handoff: {
|
|
@@ -4988,6 +5078,10 @@ var createVoiceSession = (options) => {
|
|
|
4988
5078
|
if (!result) {
|
|
4989
5079
|
return;
|
|
4990
5080
|
}
|
|
5081
|
+
if (queuedDelivery) {
|
|
5082
|
+
const updatedDelivery = applyVoiceHandoffDeliveryResult(queuedDelivery, result);
|
|
5083
|
+
await options.handoff?.deliveryQueue?.set(updatedDelivery.id, updatedDelivery);
|
|
5084
|
+
}
|
|
4991
5085
|
await appendTrace({
|
|
4992
5086
|
metadata: input.metadata,
|
|
4993
5087
|
payload: {
|
package/dist/types.d.ts
CHANGED
|
@@ -278,6 +278,34 @@ export type VoiceHandoffResult = {
|
|
|
278
278
|
metadata?: Record<string, unknown>;
|
|
279
279
|
status: VoiceHandoffStatus;
|
|
280
280
|
};
|
|
281
|
+
export type VoiceHandoffDeliveryQueueStatus = VoiceHandoffStatus | 'pending';
|
|
282
|
+
export type StoredVoiceHandoffDelivery<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
283
|
+
action: VoiceHandoffAction;
|
|
284
|
+
context: TContext;
|
|
285
|
+
createdAt: number;
|
|
286
|
+
deliveredAt?: number;
|
|
287
|
+
deliveries?: Record<string, VoiceHandoffResult & {
|
|
288
|
+
adapterId: string;
|
|
289
|
+
adapterKind?: string;
|
|
290
|
+
}>;
|
|
291
|
+
deliveryAttempts?: number;
|
|
292
|
+
deliveryError?: string;
|
|
293
|
+
deliveryStatus: VoiceHandoffDeliveryQueueStatus;
|
|
294
|
+
id: string;
|
|
295
|
+
metadata?: Record<string, unknown>;
|
|
296
|
+
reason?: string;
|
|
297
|
+
result?: TResult;
|
|
298
|
+
session: TSession;
|
|
299
|
+
sessionId: string;
|
|
300
|
+
target?: string;
|
|
301
|
+
updatedAt: number;
|
|
302
|
+
};
|
|
303
|
+
export type VoiceHandoffDeliveryStore<TDelivery extends StoredVoiceHandoffDelivery = StoredVoiceHandoffDelivery> = {
|
|
304
|
+
get: (id: string) => Promise<TDelivery | undefined> | TDelivery | undefined;
|
|
305
|
+
list: () => Promise<TDelivery[]> | TDelivery[];
|
|
306
|
+
remove: (id: string) => Promise<void> | void;
|
|
307
|
+
set: (id: string, delivery: TDelivery) => Promise<void> | void;
|
|
308
|
+
};
|
|
281
309
|
export type VoiceHandoffInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
282
310
|
action: VoiceHandoffAction;
|
|
283
311
|
api: VoiceSessionHandle<TContext, TSession, TResult>;
|
|
@@ -296,6 +324,8 @@ export type VoiceHandoffAdapter<TContext = unknown, TSession extends VoiceSessio
|
|
|
296
324
|
};
|
|
297
325
|
export type VoiceHandoffConfig<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
298
326
|
adapters: VoiceHandoffAdapter<TContext, TSession, TResult>[];
|
|
327
|
+
deliveryQueue?: VoiceHandoffDeliveryStore<StoredVoiceHandoffDelivery<TContext, TSession, TResult>>;
|
|
328
|
+
enqueueOnly?: boolean;
|
|
299
329
|
failMode?: 'record' | 'throw';
|
|
300
330
|
};
|
|
301
331
|
export type VoiceSessionStore<TSession extends VoiceSessionRecord = VoiceSessionRecord> = SessionStore<TSession, VoiceSessionSummary>;
|