@absolutejs/voice 0.0.22-beta.30 → 0.0.22-beta.32
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 +297 -4
- package/dist/modelAdapters.d.ts +6 -0
- package/dist/providerHealth.d.ts +1 -0
- package/dist/queue.d.ts +52 -0
- package/dist/testing/index.js +136 -2
- 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: {
|
|
@@ -6458,7 +6552,8 @@ var summarizeVoiceProviderHealth = async (input) => {
|
|
|
6458
6552
|
rateLimited: false,
|
|
6459
6553
|
recommended: false,
|
|
6460
6554
|
runCount: 0,
|
|
6461
|
-
status: "idle"
|
|
6555
|
+
status: "idle",
|
|
6556
|
+
timeoutCount: 0
|
|
6462
6557
|
};
|
|
6463
6558
|
entries.set(provider, entry);
|
|
6464
6559
|
return entry;
|
|
@@ -6535,6 +6630,9 @@ var summarizeVoiceProviderHealth = async (input) => {
|
|
|
6535
6630
|
}
|
|
6536
6631
|
const entry = applyProviderHealth();
|
|
6537
6632
|
entry.errorCount += 1;
|
|
6633
|
+
if (event.payload.timedOut === true) {
|
|
6634
|
+
entry.timeoutCount += 1;
|
|
6635
|
+
}
|
|
6538
6636
|
entry.lastError = getString(event.payload.error);
|
|
6539
6637
|
entry.lastErrorAt = event.at;
|
|
6540
6638
|
entry.rateLimited ||= event.payload.rateLimited === true;
|
|
@@ -6559,7 +6657,8 @@ var summarizeVoiceProviderHealth = async (input) => {
|
|
|
6559
6657
|
runCount: entry.runCount,
|
|
6560
6658
|
status,
|
|
6561
6659
|
suppressionRemainingMs: activeSuppression ? suppressionRemainingMs : undefined,
|
|
6562
|
-
suppressedUntil: entry.suppressedUntil
|
|
6660
|
+
suppressedUntil: entry.suppressedUntil,
|
|
6661
|
+
timeoutCount: entry.timeoutCount
|
|
6563
6662
|
};
|
|
6564
6663
|
});
|
|
6565
6664
|
const recommended = summaries.filter((entry) => entry.status === "healthy").sort((left, right) => (left.averageElapsedMs ?? Number.MAX_SAFE_INTEGER) - (right.averageElapsedMs ?? Number.MAX_SAFE_INTEGER))[0];
|
|
@@ -6583,6 +6682,7 @@ var renderVoiceProviderHealthHTML = (providers) => providers.length === 0 ? '<p
|
|
|
6583
6682
|
`<div><dt>Runs</dt><dd>${String(provider.runCount)}</dd></div>`,
|
|
6584
6683
|
`<div><dt>Avg latency</dt><dd>${String(provider.averageElapsedMs ?? 0)}ms</dd></div>`,
|
|
6585
6684
|
`<div><dt>Errors</dt><dd>${String(provider.errorCount)}</dd></div>`,
|
|
6685
|
+
`<div><dt>Timeouts</dt><dd>${String(provider.timeoutCount)}</dd></div>`,
|
|
6586
6686
|
`<div><dt>Fallbacks</dt><dd>${String(provider.fallbackCount)}</dd></div>`,
|
|
6587
6687
|
"</dl>",
|
|
6588
6688
|
suppressionSeconds ? `<p>Temporarily suppressed for ${String(suppressionSeconds)}s.</p>` : "",
|
|
@@ -8038,6 +8138,17 @@ var parseJSONValue = (value) => {
|
|
|
8038
8138
|
return value;
|
|
8039
8139
|
}
|
|
8040
8140
|
};
|
|
8141
|
+
|
|
8142
|
+
class VoiceProviderTimeoutError extends Error {
|
|
8143
|
+
provider;
|
|
8144
|
+
timeoutMs;
|
|
8145
|
+
constructor(provider, timeoutMs) {
|
|
8146
|
+
super(`Voice provider ${provider} exceeded ${timeoutMs}ms latency budget.`);
|
|
8147
|
+
this.name = "VoiceProviderTimeoutError";
|
|
8148
|
+
this.provider = provider;
|
|
8149
|
+
this.timeoutMs = timeoutMs;
|
|
8150
|
+
}
|
|
8151
|
+
}
|
|
8041
8152
|
var getMessageToolCalls = (message) => {
|
|
8042
8153
|
const toolCalls = message.metadata?.toolCalls;
|
|
8043
8154
|
return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
|
|
@@ -8115,6 +8226,10 @@ var createVoiceProviderRouter = (options) => {
|
|
|
8115
8226
|
const failureThreshold = Math.max(1, healthOptions?.failureThreshold ?? 1);
|
|
8116
8227
|
const cooldownMs = Math.max(0, healthOptions?.cooldownMs ?? 30000);
|
|
8117
8228
|
const rateLimitCooldownMs = Math.max(0, healthOptions?.rateLimitCooldownMs ?? 60000);
|
|
8229
|
+
const getProviderTimeoutMs = (provider) => {
|
|
8230
|
+
const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
|
|
8231
|
+
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
|
|
8232
|
+
};
|
|
8118
8233
|
const getHealth = (provider) => {
|
|
8119
8234
|
const existing = healthState.get(provider);
|
|
8120
8235
|
if (existing) {
|
|
@@ -8226,6 +8341,25 @@ var createVoiceProviderRouter = (options) => {
|
|
|
8226
8341
|
const emit = async (event, input) => {
|
|
8227
8342
|
await options.onProviderEvent?.(event, input);
|
|
8228
8343
|
};
|
|
8344
|
+
const runProvider = async (provider, model, input) => {
|
|
8345
|
+
const timeoutMs = getProviderTimeoutMs(provider);
|
|
8346
|
+
if (!timeoutMs) {
|
|
8347
|
+
return model.generate(input);
|
|
8348
|
+
}
|
|
8349
|
+
let timeout;
|
|
8350
|
+
try {
|
|
8351
|
+
return await Promise.race([
|
|
8352
|
+
model.generate(input),
|
|
8353
|
+
new Promise((_, reject) => {
|
|
8354
|
+
timeout = setTimeout(() => reject(new VoiceProviderTimeoutError(provider, timeoutMs)), timeoutMs);
|
|
8355
|
+
})
|
|
8356
|
+
]);
|
|
8357
|
+
} finally {
|
|
8358
|
+
if (timeout) {
|
|
8359
|
+
clearTimeout(timeout);
|
|
8360
|
+
}
|
|
8361
|
+
}
|
|
8362
|
+
};
|
|
8229
8363
|
return {
|
|
8230
8364
|
generate: async (input) => {
|
|
8231
8365
|
const { order, selectedProvider } = await resolveOrder(input);
|
|
@@ -8240,12 +8374,14 @@ var createVoiceProviderRouter = (options) => {
|
|
|
8240
8374
|
}
|
|
8241
8375
|
const startedAt = Date.now();
|
|
8242
8376
|
try {
|
|
8243
|
-
const output = await model
|
|
8377
|
+
const output = await runProvider(provider, model, input);
|
|
8244
8378
|
const providerHealth = recordProviderSuccess(provider);
|
|
8245
8379
|
await emit({
|
|
8246
8380
|
at: Date.now(),
|
|
8381
|
+
attempt: index + 1,
|
|
8247
8382
|
elapsedMs: Date.now() - startedAt,
|
|
8248
8383
|
fallbackProvider: provider === selectedProvider ? undefined : provider,
|
|
8384
|
+
latencyBudgetMs: getProviderTimeoutMs(provider),
|
|
8249
8385
|
provider,
|
|
8250
8386
|
providerHealth,
|
|
8251
8387
|
recovered: provider !== selectedProvider,
|
|
@@ -8257,22 +8393,26 @@ var createVoiceProviderRouter = (options) => {
|
|
|
8257
8393
|
lastError = error;
|
|
8258
8394
|
const hasNextProvider = index < order.length - 1;
|
|
8259
8395
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
8396
|
+
const timedOut = options.isTimeoutError?.(error, provider) ?? error instanceof VoiceProviderTimeoutError;
|
|
8260
8397
|
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
8261
8398
|
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
8262
8399
|
const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
|
|
8263
8400
|
const nextProvider = hasNextProvider ? order[index + 1] : undefined;
|
|
8264
8401
|
await emit({
|
|
8265
8402
|
at: Date.now(),
|
|
8403
|
+
attempt: index + 1,
|
|
8266
8404
|
elapsedMs: Date.now() - startedAt,
|
|
8267
8405
|
error: errorMessage(error),
|
|
8268
8406
|
fallbackProvider: shouldFallback ? nextProvider : undefined,
|
|
8407
|
+
latencyBudgetMs: getProviderTimeoutMs(provider),
|
|
8269
8408
|
provider,
|
|
8270
8409
|
providerHealth,
|
|
8271
8410
|
rateLimited,
|
|
8272
8411
|
selectedProvider,
|
|
8273
8412
|
suppressionRemainingMs: getSuppressionRemainingMs(provider),
|
|
8274
8413
|
suppressedUntil: providerHealth?.suppressedUntil,
|
|
8275
|
-
status: "error"
|
|
8414
|
+
status: "error",
|
|
8415
|
+
timedOut
|
|
8276
8416
|
}, input);
|
|
8277
8417
|
if (!hasNextProvider || !shouldFallback) {
|
|
8278
8418
|
throw error;
|
|
@@ -9625,6 +9765,8 @@ var shouldDeadLetterSinkEvent = (event, sinks, maxFailures) => typeof maxFailure
|
|
|
9625
9765
|
var shouldDeadLetterTask = (task, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (task.processingAttempts ?? 0) >= maxFailures;
|
|
9626
9766
|
var shouldProcessTraceDeliveryStatus = (status, allowed) => allowed.includes(status);
|
|
9627
9767
|
var shouldDeadLetterTraceDelivery = (delivery, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (delivery.deliveryAttempts ?? 0) >= maxFailures;
|
|
9768
|
+
var shouldProcessHandoffDeliveryStatus = (status, allowed) => allowed.includes(status);
|
|
9769
|
+
var shouldDeadLetterHandoffDelivery = (delivery, maxFailures) => typeof maxFailures === "number" && maxFailures > 0 && (delivery.deliveryAttempts ?? 0) >= maxFailures;
|
|
9628
9770
|
var summarizeVoiceIntegrationEvents = (events, input = {}) => {
|
|
9629
9771
|
const buildSummary = async () => {
|
|
9630
9772
|
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((event) => event.id) : []);
|
|
@@ -9706,6 +9848,48 @@ var summarizeVoiceTraceSinkDeliveries = (deliveries, input = {}) => {
|
|
|
9706
9848
|
};
|
|
9707
9849
|
return buildSummary();
|
|
9708
9850
|
};
|
|
9851
|
+
var summarizeVoiceHandoffDeliveries = (deliveries, input = {}) => {
|
|
9852
|
+
const buildSummary = async () => {
|
|
9853
|
+
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((delivery) => delivery.id) : []);
|
|
9854
|
+
const byAction = new Map;
|
|
9855
|
+
const summary = {
|
|
9856
|
+
byAction: [],
|
|
9857
|
+
deadLettered: 0,
|
|
9858
|
+
delivered: 0,
|
|
9859
|
+
failed: 0,
|
|
9860
|
+
pending: 0,
|
|
9861
|
+
retryEligible: 0,
|
|
9862
|
+
skipped: 0,
|
|
9863
|
+
total: deliveries.length
|
|
9864
|
+
};
|
|
9865
|
+
for (const delivery of deliveries) {
|
|
9866
|
+
byAction.set(delivery.action, (byAction.get(delivery.action) ?? 0) + 1);
|
|
9867
|
+
if (deadLetterIds.has(delivery.id)) {
|
|
9868
|
+
summary.deadLettered += 1;
|
|
9869
|
+
}
|
|
9870
|
+
switch (delivery.deliveryStatus) {
|
|
9871
|
+
case "delivered":
|
|
9872
|
+
summary.delivered += 1;
|
|
9873
|
+
break;
|
|
9874
|
+
case "failed":
|
|
9875
|
+
summary.failed += 1;
|
|
9876
|
+
if ((delivery.deliveryAttempts ?? 0) > 0) {
|
|
9877
|
+
summary.retryEligible += 1;
|
|
9878
|
+
}
|
|
9879
|
+
break;
|
|
9880
|
+
case "skipped":
|
|
9881
|
+
summary.skipped += 1;
|
|
9882
|
+
break;
|
|
9883
|
+
case "pending":
|
|
9884
|
+
summary.pending += 1;
|
|
9885
|
+
break;
|
|
9886
|
+
}
|
|
9887
|
+
}
|
|
9888
|
+
summary.byAction = [...byAction.entries()].sort((left, right) => right[1] - left[1]);
|
|
9889
|
+
return summary;
|
|
9890
|
+
};
|
|
9891
|
+
return buildSummary();
|
|
9892
|
+
};
|
|
9709
9893
|
var summarizeVoiceOpsTaskQueue = (tasks, input = {}) => {
|
|
9710
9894
|
const buildSummary = async () => {
|
|
9711
9895
|
const deadLetterIds = new Set(input.deadLetters ? (await input.deadLetters.list()).map((task) => task.id) : []);
|
|
@@ -10135,6 +10319,108 @@ var createVoiceTraceSinkDeliveryWorkerLoop = (options) => {
|
|
|
10135
10319
|
tick
|
|
10136
10320
|
};
|
|
10137
10321
|
};
|
|
10322
|
+
var createVoiceHandoffDeliveryWorker = (options) => {
|
|
10323
|
+
const allowedStatuses = options.statuses ?? ["pending", "failed"];
|
|
10324
|
+
const leaseMs = Math.max(1, options.leaseMs ?? 30000);
|
|
10325
|
+
return {
|
|
10326
|
+
drain: async () => {
|
|
10327
|
+
const result = {
|
|
10328
|
+
alreadyProcessed: 0,
|
|
10329
|
+
attempted: 0,
|
|
10330
|
+
deadLettered: 0,
|
|
10331
|
+
delivered: 0,
|
|
10332
|
+
failed: 0,
|
|
10333
|
+
skipped: 0
|
|
10334
|
+
};
|
|
10335
|
+
const deliveries = [...await options.deliveries.list()].sort((left, right) => left.createdAt - right.createdAt);
|
|
10336
|
+
for (const delivery of deliveries) {
|
|
10337
|
+
if (!shouldProcessHandoffDeliveryStatus(delivery.deliveryStatus, allowedStatuses)) {
|
|
10338
|
+
continue;
|
|
10339
|
+
}
|
|
10340
|
+
if (shouldDeadLetterHandoffDelivery(delivery, options.maxFailures)) {
|
|
10341
|
+
await options.deadLetters?.set(delivery.id, delivery);
|
|
10342
|
+
await options.onDeadLetter?.(delivery);
|
|
10343
|
+
result.deadLettered += 1;
|
|
10344
|
+
continue;
|
|
10345
|
+
}
|
|
10346
|
+
const claimed = await options.leases.claim({
|
|
10347
|
+
leaseMs,
|
|
10348
|
+
taskId: delivery.id,
|
|
10349
|
+
workerId: options.workerId
|
|
10350
|
+
});
|
|
10351
|
+
if (!claimed) {
|
|
10352
|
+
continue;
|
|
10353
|
+
}
|
|
10354
|
+
try {
|
|
10355
|
+
const idempotencyKey = `${delivery.id}:handoff`;
|
|
10356
|
+
if (options.idempotency && await options.idempotency.has(idempotencyKey)) {
|
|
10357
|
+
result.alreadyProcessed += 1;
|
|
10358
|
+
continue;
|
|
10359
|
+
}
|
|
10360
|
+
result.attempted += 1;
|
|
10361
|
+
const updatedDelivery = await deliverVoiceHandoffDelivery({
|
|
10362
|
+
adapters: options.adapters,
|
|
10363
|
+
api: options.api,
|
|
10364
|
+
delivery,
|
|
10365
|
+
failMode: options.failMode
|
|
10366
|
+
});
|
|
10367
|
+
await options.deliveries.set(updatedDelivery.id, updatedDelivery);
|
|
10368
|
+
if (updatedDelivery.deliveryStatus === "delivered" || updatedDelivery.deliveryStatus === "skipped") {
|
|
10369
|
+
await options.idempotency?.set(idempotencyKey, {
|
|
10370
|
+
ttlSeconds: options.idempotencyTtlSeconds
|
|
10371
|
+
});
|
|
10372
|
+
}
|
|
10373
|
+
if (updatedDelivery.deliveryStatus === "delivered") {
|
|
10374
|
+
result.delivered += 1;
|
|
10375
|
+
} else if (updatedDelivery.deliveryStatus === "skipped") {
|
|
10376
|
+
result.skipped += 1;
|
|
10377
|
+
} else if (updatedDelivery.deliveryStatus === "failed") {
|
|
10378
|
+
result.failed += 1;
|
|
10379
|
+
if (shouldDeadLetterHandoffDelivery(updatedDelivery, options.maxFailures)) {
|
|
10380
|
+
await options.deadLetters?.set(updatedDelivery.id, updatedDelivery);
|
|
10381
|
+
await options.onDeadLetter?.(updatedDelivery);
|
|
10382
|
+
result.deadLettered += 1;
|
|
10383
|
+
}
|
|
10384
|
+
}
|
|
10385
|
+
} finally {
|
|
10386
|
+
await options.leases.release({
|
|
10387
|
+
taskId: delivery.id,
|
|
10388
|
+
workerId: options.workerId
|
|
10389
|
+
});
|
|
10390
|
+
}
|
|
10391
|
+
}
|
|
10392
|
+
return result;
|
|
10393
|
+
}
|
|
10394
|
+
};
|
|
10395
|
+
};
|
|
10396
|
+
var createVoiceHandoffDeliveryWorkerLoop = (options) => {
|
|
10397
|
+
const pollIntervalMs = Math.max(1, options.pollIntervalMs ?? 1000);
|
|
10398
|
+
let timer;
|
|
10399
|
+
let running = false;
|
|
10400
|
+
const tick = async () => options.worker.drain();
|
|
10401
|
+
return {
|
|
10402
|
+
isRunning: () => running,
|
|
10403
|
+
start: () => {
|
|
10404
|
+
if (timer) {
|
|
10405
|
+
return;
|
|
10406
|
+
}
|
|
10407
|
+
running = true;
|
|
10408
|
+
timer = setInterval(() => {
|
|
10409
|
+
tick().catch((error) => {
|
|
10410
|
+
options.onError?.(error);
|
|
10411
|
+
});
|
|
10412
|
+
}, pollIntervalMs);
|
|
10413
|
+
},
|
|
10414
|
+
stop: () => {
|
|
10415
|
+
if (timer) {
|
|
10416
|
+
clearInterval(timer);
|
|
10417
|
+
timer = undefined;
|
|
10418
|
+
}
|
|
10419
|
+
running = false;
|
|
10420
|
+
},
|
|
10421
|
+
tick
|
|
10422
|
+
};
|
|
10423
|
+
};
|
|
10138
10424
|
var createVoiceOpsTaskWorker = (options) => {
|
|
10139
10425
|
const leaseMs = Math.max(1, options.leaseMs ?? 30000);
|
|
10140
10426
|
const getTask = async (taskId) => {
|
|
@@ -11532,6 +11818,7 @@ export {
|
|
|
11532
11818
|
summarizeVoiceOpsTaskAnalytics,
|
|
11533
11819
|
summarizeVoiceIntegrationEvents,
|
|
11534
11820
|
summarizeVoiceHandoffHealth,
|
|
11821
|
+
summarizeVoiceHandoffDeliveries,
|
|
11535
11822
|
summarizeVoiceAssistantRuns,
|
|
11536
11823
|
summarizeVoiceAssistantHealth,
|
|
11537
11824
|
startVoiceOpsTask,
|
|
@@ -11577,6 +11864,7 @@ export {
|
|
|
11577
11864
|
deliverVoiceTraceEventsToSinks,
|
|
11578
11865
|
deliverVoiceIntegrationEventToSinks,
|
|
11579
11866
|
deliverVoiceIntegrationEvent,
|
|
11867
|
+
deliverVoiceHandoffDelivery,
|
|
11580
11868
|
deliverVoiceHandoff,
|
|
11581
11869
|
decodeTwilioMulawBase64,
|
|
11582
11870
|
deadLetterVoiceOpsTask,
|
|
@@ -11641,6 +11929,7 @@ export {
|
|
|
11641
11929
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
11642
11930
|
createVoiceMemoryTraceEventStore,
|
|
11643
11931
|
createVoiceMemoryStore,
|
|
11932
|
+
createVoiceMemoryHandoffDeliveryStore,
|
|
11644
11933
|
createVoiceMemoryAssistantMemoryStore,
|
|
11645
11934
|
createVoiceLinearIssueUpdateSink,
|
|
11646
11935
|
createVoiceLinearIssueSyncSinks,
|
|
@@ -11656,6 +11945,9 @@ export {
|
|
|
11656
11945
|
createVoiceHandoffHealthRoutes,
|
|
11657
11946
|
createVoiceHandoffHealthJSONHandler,
|
|
11658
11947
|
createVoiceHandoffHealthHTMLHandler,
|
|
11948
|
+
createVoiceHandoffDeliveryWorkerLoop,
|
|
11949
|
+
createVoiceHandoffDeliveryWorker,
|
|
11950
|
+
createVoiceHandoffDeliveryRecord,
|
|
11659
11951
|
createVoiceFileTraceSinkDeliveryStore,
|
|
11660
11952
|
createVoiceFileTraceEventStore,
|
|
11661
11953
|
createVoiceFileTaskStore,
|
|
@@ -11706,6 +11998,7 @@ export {
|
|
|
11706
11998
|
assignVoiceOpsTask,
|
|
11707
11999
|
applyVoiceOpsTaskPolicy,
|
|
11708
12000
|
applyVoiceOpsTaskAssignmentRule,
|
|
12001
|
+
applyVoiceHandoffDeliveryResult,
|
|
11709
12002
|
applyRiskTieredPhraseHintCorrections,
|
|
11710
12003
|
applyPhraseHintCorrections,
|
|
11711
12004
|
TURN_PROFILE_DEFAULTS
|
package/dist/modelAdapters.d.ts
CHANGED
|
@@ -36,9 +36,11 @@ export type GeminiVoiceAssistantModelOptions = {
|
|
|
36
36
|
};
|
|
37
37
|
export type VoiceProviderRouterEvent<TProvider extends string = string> = {
|
|
38
38
|
at: number;
|
|
39
|
+
attempt: number;
|
|
39
40
|
elapsedMs: number;
|
|
40
41
|
error?: string;
|
|
41
42
|
fallbackProvider?: TProvider;
|
|
43
|
+
latencyBudgetMs?: number;
|
|
42
44
|
provider: TProvider;
|
|
43
45
|
providerHealth?: VoiceProviderRouterProviderHealth<TProvider>;
|
|
44
46
|
rateLimited?: boolean;
|
|
@@ -47,6 +49,7 @@ export type VoiceProviderRouterEvent<TProvider extends string = string> = {
|
|
|
47
49
|
suppressionRemainingMs?: number;
|
|
48
50
|
suppressedUntil?: number;
|
|
49
51
|
status: 'error' | 'fallback' | 'success';
|
|
52
|
+
timedOut?: boolean;
|
|
50
53
|
};
|
|
51
54
|
export type VoiceProviderRouterFallbackMode = 'never' | 'provider-error' | 'rate-limit';
|
|
52
55
|
export type VoiceProviderRouterPolicy<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TProvider extends string = string> = 'ordered' | 'prefer-cheapest' | 'prefer-fastest' | 'prefer-selected' | {
|
|
@@ -58,6 +61,7 @@ export type VoiceProviderRouterProviderProfile = {
|
|
|
58
61
|
cost?: number;
|
|
59
62
|
latencyMs?: number;
|
|
60
63
|
priority?: number;
|
|
64
|
+
timeoutMs?: number;
|
|
61
65
|
};
|
|
62
66
|
export type VoiceProviderRouterHealthOptions = {
|
|
63
67
|
cooldownMs?: number;
|
|
@@ -79,10 +83,12 @@ export type VoiceProviderRouterOptions<TContext = unknown, TSession extends Voic
|
|
|
79
83
|
fallbackMode?: VoiceProviderRouterFallbackMode;
|
|
80
84
|
isProviderError?: (error: unknown, provider: TProvider) => boolean;
|
|
81
85
|
isRateLimitError?: (error: unknown, provider: TProvider) => boolean;
|
|
86
|
+
isTimeoutError?: (error: unknown, provider: TProvider) => boolean;
|
|
82
87
|
onProviderEvent?: (event: VoiceProviderRouterEvent<TProvider>, input: VoiceAgentModelInput<TContext, TSession>) => Promise<void> | void;
|
|
83
88
|
policy?: VoiceProviderRouterPolicy<TContext, TSession, TProvider>;
|
|
84
89
|
providerHealth?: boolean | VoiceProviderRouterHealthOptions;
|
|
85
90
|
providerProfiles?: Partial<Record<TProvider, VoiceProviderRouterProviderProfile>>;
|
|
91
|
+
timeoutMs?: number;
|
|
86
92
|
providers: Partial<Record<TProvider, VoiceAgentModel<TContext, TSession, TResult>>>;
|
|
87
93
|
selectProvider?: (input: VoiceAgentModelInput<TContext, TSession>) => TProvider | undefined | Promise<TProvider | undefined>;
|
|
88
94
|
};
|
package/dist/providerHealth.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type VoiceProviderHealthSummary<TProvider extends string = string> = {
|
|
|
15
15
|
status: VoiceProviderHealthStatus;
|
|
16
16
|
suppressionRemainingMs?: number;
|
|
17
17
|
suppressedUntil?: number;
|
|
18
|
+
timeoutCount: number;
|
|
18
19
|
};
|
|
19
20
|
export type VoiceProviderHealthSummaryOptions<TProvider extends string = string> = {
|
|
20
21
|
events?: StoredVoiceTraceEvent[];
|
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
|
@@ -3601,6 +3601,17 @@ var parseJSONValue = (value) => {
|
|
|
3601
3601
|
return value;
|
|
3602
3602
|
}
|
|
3603
3603
|
};
|
|
3604
|
+
|
|
3605
|
+
class VoiceProviderTimeoutError extends Error {
|
|
3606
|
+
provider;
|
|
3607
|
+
timeoutMs;
|
|
3608
|
+
constructor(provider, timeoutMs) {
|
|
3609
|
+
super(`Voice provider ${provider} exceeded ${timeoutMs}ms latency budget.`);
|
|
3610
|
+
this.name = "VoiceProviderTimeoutError";
|
|
3611
|
+
this.provider = provider;
|
|
3612
|
+
this.timeoutMs = timeoutMs;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3604
3615
|
var getMessageToolCalls = (message) => {
|
|
3605
3616
|
const toolCalls = message.metadata?.toolCalls;
|
|
3606
3617
|
return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
|
|
@@ -3678,6 +3689,10 @@ var createVoiceProviderRouter = (options) => {
|
|
|
3678
3689
|
const failureThreshold = Math.max(1, healthOptions?.failureThreshold ?? 1);
|
|
3679
3690
|
const cooldownMs = Math.max(0, healthOptions?.cooldownMs ?? 30000);
|
|
3680
3691
|
const rateLimitCooldownMs = Math.max(0, healthOptions?.rateLimitCooldownMs ?? 60000);
|
|
3692
|
+
const getProviderTimeoutMs = (provider) => {
|
|
3693
|
+
const timeoutMs = options.providerProfiles?.[provider]?.timeoutMs ?? options.timeoutMs;
|
|
3694
|
+
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : undefined;
|
|
3695
|
+
};
|
|
3681
3696
|
const getHealth = (provider) => {
|
|
3682
3697
|
const existing = healthState.get(provider);
|
|
3683
3698
|
if (existing) {
|
|
@@ -3789,6 +3804,25 @@ var createVoiceProviderRouter = (options) => {
|
|
|
3789
3804
|
const emit = async (event, input) => {
|
|
3790
3805
|
await options.onProviderEvent?.(event, input);
|
|
3791
3806
|
};
|
|
3807
|
+
const runProvider = async (provider, model, input) => {
|
|
3808
|
+
const timeoutMs = getProviderTimeoutMs(provider);
|
|
3809
|
+
if (!timeoutMs) {
|
|
3810
|
+
return model.generate(input);
|
|
3811
|
+
}
|
|
3812
|
+
let timeout;
|
|
3813
|
+
try {
|
|
3814
|
+
return await Promise.race([
|
|
3815
|
+
model.generate(input),
|
|
3816
|
+
new Promise((_, reject) => {
|
|
3817
|
+
timeout = setTimeout(() => reject(new VoiceProviderTimeoutError(provider, timeoutMs)), timeoutMs);
|
|
3818
|
+
})
|
|
3819
|
+
]);
|
|
3820
|
+
} finally {
|
|
3821
|
+
if (timeout) {
|
|
3822
|
+
clearTimeout(timeout);
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3792
3826
|
return {
|
|
3793
3827
|
generate: async (input) => {
|
|
3794
3828
|
const { order, selectedProvider } = await resolveOrder(input);
|
|
@@ -3803,12 +3837,14 @@ var createVoiceProviderRouter = (options) => {
|
|
|
3803
3837
|
}
|
|
3804
3838
|
const startedAt = Date.now();
|
|
3805
3839
|
try {
|
|
3806
|
-
const output = await model
|
|
3840
|
+
const output = await runProvider(provider, model, input);
|
|
3807
3841
|
const providerHealth = recordProviderSuccess(provider);
|
|
3808
3842
|
await emit({
|
|
3809
3843
|
at: Date.now(),
|
|
3844
|
+
attempt: index + 1,
|
|
3810
3845
|
elapsedMs: Date.now() - startedAt,
|
|
3811
3846
|
fallbackProvider: provider === selectedProvider ? undefined : provider,
|
|
3847
|
+
latencyBudgetMs: getProviderTimeoutMs(provider),
|
|
3812
3848
|
provider,
|
|
3813
3849
|
providerHealth,
|
|
3814
3850
|
recovered: provider !== selectedProvider,
|
|
@@ -3820,22 +3856,26 @@ var createVoiceProviderRouter = (options) => {
|
|
|
3820
3856
|
lastError = error;
|
|
3821
3857
|
const hasNextProvider = index < order.length - 1;
|
|
3822
3858
|
const isProviderError = options.isProviderError?.(error, provider) ?? true;
|
|
3859
|
+
const timedOut = options.isTimeoutError?.(error, provider) ?? error instanceof VoiceProviderTimeoutError;
|
|
3823
3860
|
const rateLimited = options.isRateLimitError?.(error, provider) ?? defaultIsRateLimitError(error);
|
|
3824
3861
|
const shouldFallback = fallbackMode === "provider-error" ? isProviderError : fallbackMode === "rate-limit" ? isProviderError && rateLimited : false;
|
|
3825
3862
|
const providerHealth = recordProviderError(provider, isProviderError, rateLimited);
|
|
3826
3863
|
const nextProvider = hasNextProvider ? order[index + 1] : undefined;
|
|
3827
3864
|
await emit({
|
|
3828
3865
|
at: Date.now(),
|
|
3866
|
+
attempt: index + 1,
|
|
3829
3867
|
elapsedMs: Date.now() - startedAt,
|
|
3830
3868
|
error: errorMessage(error),
|
|
3831
3869
|
fallbackProvider: shouldFallback ? nextProvider : undefined,
|
|
3870
|
+
latencyBudgetMs: getProviderTimeoutMs(provider),
|
|
3832
3871
|
provider,
|
|
3833
3872
|
providerHealth,
|
|
3834
3873
|
rateLimited,
|
|
3835
3874
|
selectedProvider,
|
|
3836
3875
|
suppressionRemainingMs: getSuppressionRemainingMs(provider),
|
|
3837
3876
|
suppressedUntil: providerHealth?.suppressedUntil,
|
|
3838
|
-
status: "error"
|
|
3877
|
+
status: "error",
|
|
3878
|
+
timedOut
|
|
3839
3879
|
}, input);
|
|
3840
3880
|
if (!hasNextProvider || !shouldFallback) {
|
|
3841
3881
|
throw error;
|
|
@@ -4486,6 +4526,14 @@ var aggregateHandoffStatus = (deliveries) => {
|
|
|
4486
4526
|
}
|
|
4487
4527
|
return "skipped";
|
|
4488
4528
|
};
|
|
4529
|
+
var createHandoffDeliveryId = (input) => [
|
|
4530
|
+
"voice-handoff",
|
|
4531
|
+
input.sessionId,
|
|
4532
|
+
input.action,
|
|
4533
|
+
Date.now(),
|
|
4534
|
+
crypto.randomUUID()
|
|
4535
|
+
].join(":");
|
|
4536
|
+
var resolveHandoffDeliveryError = (deliveries) => Object.values(deliveries).map((delivery) => delivery.error).find(Boolean);
|
|
4489
4537
|
var defaultWebhookBody = (input) => ({
|
|
4490
4538
|
action: input.action,
|
|
4491
4539
|
metadata: input.metadata,
|
|
@@ -4534,6 +4582,73 @@ var deliverVoiceHandoff = async (input) => {
|
|
|
4534
4582
|
status: aggregateHandoffStatus(deliveries)
|
|
4535
4583
|
};
|
|
4536
4584
|
};
|
|
4585
|
+
var createVoiceHandoffDeliveryRecord = (input) => {
|
|
4586
|
+
const now = Date.now();
|
|
4587
|
+
return {
|
|
4588
|
+
action: input.action,
|
|
4589
|
+
context: input.context,
|
|
4590
|
+
createdAt: now,
|
|
4591
|
+
deliveryAttempts: 0,
|
|
4592
|
+
deliveryStatus: "pending",
|
|
4593
|
+
id: input.id ?? createHandoffDeliveryId({
|
|
4594
|
+
action: input.action,
|
|
4595
|
+
sessionId: input.session.id
|
|
4596
|
+
}),
|
|
4597
|
+
metadata: input.metadata,
|
|
4598
|
+
reason: input.reason,
|
|
4599
|
+
result: input.result,
|
|
4600
|
+
session: input.session,
|
|
4601
|
+
sessionId: input.session.id,
|
|
4602
|
+
target: input.target,
|
|
4603
|
+
updatedAt: now
|
|
4604
|
+
};
|
|
4605
|
+
};
|
|
4606
|
+
var applyVoiceHandoffDeliveryResult = (delivery, result) => ({
|
|
4607
|
+
...delivery,
|
|
4608
|
+
deliveredAt: result.status === "delivered" || result.status === "skipped" ? Date.now() : delivery.deliveredAt,
|
|
4609
|
+
deliveries: result.deliveries,
|
|
4610
|
+
deliveryAttempts: (delivery.deliveryAttempts ?? 0) + 1,
|
|
4611
|
+
deliveryError: result.status === "failed" ? resolveHandoffDeliveryError(result.deliveries) : undefined,
|
|
4612
|
+
deliveryStatus: result.status,
|
|
4613
|
+
updatedAt: Date.now()
|
|
4614
|
+
});
|
|
4615
|
+
var deliverVoiceHandoffDelivery = async (options) => {
|
|
4616
|
+
const result = await deliverVoiceHandoff({
|
|
4617
|
+
config: {
|
|
4618
|
+
adapters: options.adapters,
|
|
4619
|
+
failMode: options.failMode
|
|
4620
|
+
},
|
|
4621
|
+
handoff: {
|
|
4622
|
+
action: options.delivery.action,
|
|
4623
|
+
api: options.api,
|
|
4624
|
+
context: options.delivery.context,
|
|
4625
|
+
metadata: options.delivery.metadata,
|
|
4626
|
+
reason: options.delivery.reason,
|
|
4627
|
+
result: options.delivery.result,
|
|
4628
|
+
session: options.delivery.session,
|
|
4629
|
+
target: options.delivery.target
|
|
4630
|
+
}
|
|
4631
|
+
});
|
|
4632
|
+
return result ? applyVoiceHandoffDeliveryResult(options.delivery, result) : {
|
|
4633
|
+
...options.delivery,
|
|
4634
|
+
deliveryAttempts: (options.delivery.deliveryAttempts ?? 0) + 1,
|
|
4635
|
+
deliveryStatus: "skipped",
|
|
4636
|
+
updatedAt: Date.now()
|
|
4637
|
+
};
|
|
4638
|
+
};
|
|
4639
|
+
var createVoiceMemoryHandoffDeliveryStore = () => {
|
|
4640
|
+
const deliveries = new Map;
|
|
4641
|
+
return {
|
|
4642
|
+
get: async (id) => deliveries.get(id),
|
|
4643
|
+
list: async () => [...deliveries.values()].sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id)),
|
|
4644
|
+
remove: async (id) => {
|
|
4645
|
+
deliveries.delete(id);
|
|
4646
|
+
},
|
|
4647
|
+
set: async (id, delivery) => {
|
|
4648
|
+
deliveries.set(id, delivery);
|
|
4649
|
+
}
|
|
4650
|
+
};
|
|
4651
|
+
};
|
|
4537
4652
|
var createVoiceWebhookHandoffAdapter = (options) => ({
|
|
4538
4653
|
actions: options.actions,
|
|
4539
4654
|
handoff: async (input) => {
|
|
@@ -4972,6 +5087,21 @@ var createVoiceSession = (options) => {
|
|
|
4972
5087
|
});
|
|
4973
5088
|
};
|
|
4974
5089
|
const runHandoff = async (input) => {
|
|
5090
|
+
const queuedDelivery = options.handoff?.deliveryQueue ? createVoiceHandoffDeliveryRecord({
|
|
5091
|
+
action: input.action,
|
|
5092
|
+
context: options.context,
|
|
5093
|
+
metadata: input.metadata,
|
|
5094
|
+
reason: input.reason,
|
|
5095
|
+
result: input.result,
|
|
5096
|
+
session: input.session,
|
|
5097
|
+
target: input.target
|
|
5098
|
+
}) : undefined;
|
|
5099
|
+
if (queuedDelivery) {
|
|
5100
|
+
await options.handoff?.deliveryQueue?.set(queuedDelivery.id, queuedDelivery);
|
|
5101
|
+
}
|
|
5102
|
+
if (options.handoff?.enqueueOnly) {
|
|
5103
|
+
return;
|
|
5104
|
+
}
|
|
4975
5105
|
const result = await deliverVoiceHandoff({
|
|
4976
5106
|
config: options.handoff,
|
|
4977
5107
|
handoff: {
|
|
@@ -4988,6 +5118,10 @@ var createVoiceSession = (options) => {
|
|
|
4988
5118
|
if (!result) {
|
|
4989
5119
|
return;
|
|
4990
5120
|
}
|
|
5121
|
+
if (queuedDelivery) {
|
|
5122
|
+
const updatedDelivery = applyVoiceHandoffDeliveryResult(queuedDelivery, result);
|
|
5123
|
+
await options.handoff?.deliveryQueue?.set(updatedDelivery.id, updatedDelivery);
|
|
5124
|
+
}
|
|
4991
5125
|
await appendTrace({
|
|
4992
5126
|
metadata: input.metadata,
|
|
4993
5127
|
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>;
|