@absolutejs/voice 0.0.22-beta.1 → 0.0.22-beta.3
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/README.md +82 -0
- package/dist/assistant.d.ts +123 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +564 -224
- package/dist/trace.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,88 @@ const app = new Elysia()
|
|
|
73
73
|
|
|
74
74
|
`createVoiceMemoryStore()` is dev-only. Real deployments should provide a shared store backed by Redis, Postgres, or equivalent.
|
|
75
75
|
|
|
76
|
+
## Voice Assistants
|
|
77
|
+
|
|
78
|
+
Use `createVoiceAssistant(...)` when you want one product-level surface for a voice agent instead of wiring tools, guardrails, experiments, traces, and ops recipes separately. It returns a standard `onTurn` handler, plus an `ops` object you can pass to `voice(...)`.
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
createVoiceAssistant,
|
|
83
|
+
createVoiceExperiment,
|
|
84
|
+
createVoiceFileRuntimeStorage,
|
|
85
|
+
createVoiceMemoryStore,
|
|
86
|
+
createVoiceAgentTool,
|
|
87
|
+
voice
|
|
88
|
+
} from '@absolutejs/voice';
|
|
89
|
+
import { deepgram } from '@absolutejs/voice-deepgram';
|
|
90
|
+
|
|
91
|
+
const runtimeStorage = createVoiceFileRuntimeStorage({
|
|
92
|
+
directory: '.voice-runtime/support'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const lookupOrder = createVoiceAgentTool({
|
|
96
|
+
name: 'lookup_order',
|
|
97
|
+
description: 'Look up an order by id.',
|
|
98
|
+
execute: async ({ args }) => ({ orderId: args.orderId, status: 'shipped' })
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const assistant = createVoiceAssistant({
|
|
102
|
+
id: 'support',
|
|
103
|
+
artifactPlan: {
|
|
104
|
+
ops: {
|
|
105
|
+
events: runtimeStorage.events,
|
|
106
|
+
reviews: runtimeStorage.reviews,
|
|
107
|
+
tasks: runtimeStorage.tasks
|
|
108
|
+
},
|
|
109
|
+
preset: {
|
|
110
|
+
name: 'support-triage',
|
|
111
|
+
options: {
|
|
112
|
+
queue: 'support-triage'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
experiment: createVoiceExperiment({
|
|
117
|
+
id: 'support-prompt',
|
|
118
|
+
variants: [
|
|
119
|
+
{ id: 'baseline', weight: 1 },
|
|
120
|
+
{
|
|
121
|
+
id: 'direct',
|
|
122
|
+
weight: 1,
|
|
123
|
+
system: 'You are concise, practical, and resolve the caller quickly.'
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
}),
|
|
127
|
+
guardrails: {
|
|
128
|
+
beforeTurn: ({ turn }) =>
|
|
129
|
+
turn.text.toLowerCase().includes('human')
|
|
130
|
+
? { escalate: { reason: 'caller requested a human' } }
|
|
131
|
+
: undefined
|
|
132
|
+
},
|
|
133
|
+
model: {
|
|
134
|
+
async generate({ messages, tools }) {
|
|
135
|
+
return {
|
|
136
|
+
assistantText: `I can help. Available tools: ${tools.map((tool) => tool.name).join(', ')}`
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
system: 'You are a support voice assistant.',
|
|
141
|
+
tools: [lookupOrder],
|
|
142
|
+
trace: runtimeStorage.traces
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
voice({
|
|
146
|
+
path: '/voice',
|
|
147
|
+
session: createVoiceMemoryStore(),
|
|
148
|
+
stt: deepgram({ apiKey: process.env.DEEPGRAM_API_KEY! }),
|
|
149
|
+
trace: runtimeStorage.traces,
|
|
150
|
+
ops: assistant.ops,
|
|
151
|
+
onTurn: assistant.onTurn,
|
|
152
|
+
onComplete: async () => {}
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Assistant experiments are deterministic by session id, so a caller stays on the same variant for a call. Variants can change the model, system prompt, tools, and tool-round budget; guardrails can block a turn before model execution or rewrite the returned `VoiceRouteResult`.
|
|
157
|
+
|
|
76
158
|
## Agent Tools And Squads
|
|
77
159
|
|
|
78
160
|
For assistant-style products, use `createVoiceAgent(...)` as the `onTurn` handler. The agent layer is provider-neutral: plug in any model adapter, register server-side tools, and return normal voice route results like `assistantText`, `transfer`, `escalate`, or `complete`.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type VoiceAgent, type VoiceAgentModel, type VoiceAgentOptions, type VoiceAgentSquadOptions, type VoiceAgentTool } from './agent';
|
|
2
|
+
import { type VoiceOutcomeRecipeName, type VoiceOutcomeRecipeOptions } from './outcomeRecipes';
|
|
3
|
+
import type { VoiceNormalizedRouteConfig, VoiceOnTurnObjectHandler, VoiceRouteConfig, VoiceRouteResult, VoiceRuntimeOpsConfig, VoiceSessionRecord } from './types';
|
|
4
|
+
import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
5
|
+
export type VoiceAssistantPreset = VoiceOutcomeRecipeName;
|
|
6
|
+
export type VoiceAssistantArtifactPlan<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
7
|
+
ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
|
|
8
|
+
preset?: VoiceAssistantPreset | {
|
|
9
|
+
name: VoiceAssistantPreset;
|
|
10
|
+
options?: VoiceOutcomeRecipeOptions;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type VoiceAssistantGuardrailInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = Parameters<VoiceOnTurnObjectHandler<TContext, TSession, TResult>>[0] & {
|
|
14
|
+
assistantId: string;
|
|
15
|
+
};
|
|
16
|
+
export type VoiceAssistantOutputGuardrailInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = VoiceAssistantGuardrailInput<TContext, TSession, TResult> & {
|
|
17
|
+
result: VoiceRouteResult<TResult>;
|
|
18
|
+
};
|
|
19
|
+
export type VoiceAssistantGuardrails<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
20
|
+
beforeTurn?: (input: VoiceAssistantGuardrailInput<TContext, TSession, TResult>) => Promise<VoiceRouteResult<TResult> | void> | VoiceRouteResult<TResult> | void;
|
|
21
|
+
afterTurn?: (input: VoiceAssistantOutputGuardrailInput<TContext, TSession, TResult>) => Promise<VoiceRouteResult<TResult> | void> | VoiceRouteResult<TResult> | void;
|
|
22
|
+
};
|
|
23
|
+
export type VoiceAssistantVariant<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
24
|
+
id: string;
|
|
25
|
+
maxToolRounds?: number;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
model?: VoiceAgentModel<TContext, TSession, TResult>;
|
|
28
|
+
system?: VoiceAgentOptions<TContext, TSession, TResult>['system'];
|
|
29
|
+
tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
|
|
30
|
+
weight?: number;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceAssistantExperimentResolverInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord> = {
|
|
33
|
+
assistantId: string;
|
|
34
|
+
context: TContext;
|
|
35
|
+
session: TSession;
|
|
36
|
+
turnId?: string;
|
|
37
|
+
};
|
|
38
|
+
export type VoiceAssistantExperiment<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
39
|
+
id: string;
|
|
40
|
+
resolve: (input: VoiceAssistantExperimentResolverInput<TContext, TSession>) => VoiceAssistantVariant<TContext, TSession, TResult>;
|
|
41
|
+
variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
|
|
42
|
+
};
|
|
43
|
+
export type VoiceAssistantExperimentOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
44
|
+
id: string;
|
|
45
|
+
selectVariant?: (input: VoiceAssistantExperimentResolverInput<TContext, TSession> & {
|
|
46
|
+
variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
|
|
47
|
+
}) => VoiceAssistantVariant<TContext, TSession, TResult> | string | void;
|
|
48
|
+
variants: Array<VoiceAssistantVariant<TContext, TSession, TResult>>;
|
|
49
|
+
};
|
|
50
|
+
type VoiceAssistantAgentSource<TContext, TSession extends VoiceSessionRecord, TResult> = {
|
|
51
|
+
agent: VoiceAgent<TContext, TSession, TResult>;
|
|
52
|
+
agents?: never;
|
|
53
|
+
defaultAgentId?: never;
|
|
54
|
+
maxHandoffsPerTurn?: never;
|
|
55
|
+
maxToolRounds?: never;
|
|
56
|
+
model?: never;
|
|
57
|
+
selectAgent?: never;
|
|
58
|
+
system?: never;
|
|
59
|
+
tools?: never;
|
|
60
|
+
} | {
|
|
61
|
+
agent?: never;
|
|
62
|
+
agents: Array<VoiceAgent<TContext, TSession, TResult>>;
|
|
63
|
+
defaultAgentId: string;
|
|
64
|
+
maxHandoffsPerTurn?: number;
|
|
65
|
+
maxToolRounds?: never;
|
|
66
|
+
model?: never;
|
|
67
|
+
selectAgent?: VoiceAgentSquadOptions<TContext, TSession, TResult>['selectAgent'];
|
|
68
|
+
system?: never;
|
|
69
|
+
tools?: never;
|
|
70
|
+
} | {
|
|
71
|
+
agent?: never;
|
|
72
|
+
agents?: never;
|
|
73
|
+
defaultAgentId?: never;
|
|
74
|
+
maxHandoffsPerTurn?: never;
|
|
75
|
+
maxToolRounds?: number;
|
|
76
|
+
model: VoiceAgentModel<TContext, TSession, TResult>;
|
|
77
|
+
selectAgent?: never;
|
|
78
|
+
system?: VoiceAgentOptions<TContext, TSession, TResult>['system'];
|
|
79
|
+
tools?: Array<VoiceAgentTool<TContext, TSession, Record<string, unknown>, unknown, TResult>>;
|
|
80
|
+
};
|
|
81
|
+
export type VoiceAssistantOptions<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = VoiceAssistantAgentSource<TContext, TSession, TResult> & {
|
|
82
|
+
artifactPlan?: VoiceAssistantArtifactPlan<TContext, TSession, TResult>;
|
|
83
|
+
experiment?: VoiceAssistantExperiment<TContext, TSession, TResult>;
|
|
84
|
+
guardrails?: VoiceAssistantGuardrails<TContext, TSession, TResult>;
|
|
85
|
+
id: string;
|
|
86
|
+
ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
|
|
87
|
+
trace?: VoiceAgentOptions<TContext, TSession, TResult>['trace'];
|
|
88
|
+
};
|
|
89
|
+
export type VoiceAssistant<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
|
|
90
|
+
agent: VoiceAgent<TContext, TSession, TResult>;
|
|
91
|
+
id: string;
|
|
92
|
+
onTurn: VoiceOnTurnObjectHandler<TContext, TSession, TResult>;
|
|
93
|
+
ops?: VoiceRuntimeOpsConfig<TContext, TSession, TResult>;
|
|
94
|
+
route: (overrides: Omit<VoiceRouteConfig<TContext, TSession, TResult>, 'onComplete' | 'onTurn'> & {
|
|
95
|
+
onComplete?: VoiceRouteConfig<TContext, TSession, TResult>['onComplete'];
|
|
96
|
+
}) => VoiceNormalizedRouteConfig<TContext, TSession, TResult>;
|
|
97
|
+
};
|
|
98
|
+
export type VoiceAssistantRunSummary = {
|
|
99
|
+
assistantId: string;
|
|
100
|
+
artifactPlans: Record<string, number>;
|
|
101
|
+
averageElapsedMs?: number;
|
|
102
|
+
blockedGuardrailCount: number;
|
|
103
|
+
escalationCount: number;
|
|
104
|
+
experiments: Record<string, number>;
|
|
105
|
+
guardrailCount: number;
|
|
106
|
+
outcomes: Record<string, number>;
|
|
107
|
+
runCount: number;
|
|
108
|
+
sessions: number;
|
|
109
|
+
toolCalls: Record<string, number>;
|
|
110
|
+
transferCount: number;
|
|
111
|
+
variants: Record<string, number>;
|
|
112
|
+
};
|
|
113
|
+
export type VoiceAssistantRunsSummary = {
|
|
114
|
+
assistants: VoiceAssistantRunSummary[];
|
|
115
|
+
totalRuns: number;
|
|
116
|
+
};
|
|
117
|
+
export declare const createVoiceExperiment: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAssistantExperimentOptions<TContext, TSession, TResult>) => VoiceAssistantExperiment<TContext, TSession, TResult>;
|
|
118
|
+
export declare const createVoiceAssistant: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: VoiceAssistantOptions<TContext, TSession, TResult>) => VoiceAssistant<TContext, TSession, TResult>;
|
|
119
|
+
export declare const summarizeVoiceAssistantRuns: (input: StoredVoiceTraceEvent[] | {
|
|
120
|
+
events?: StoredVoiceTraceEvent[];
|
|
121
|
+
store?: VoiceTraceEventStore;
|
|
122
|
+
}) => Promise<VoiceAssistantRunsSummary>;
|
|
123
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { voice } from './plugin';
|
|
2
|
+
export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
|
|
2
3
|
export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool } from './agent';
|
|
3
4
|
export { createStoredVoiceCallReviewArtifact, createStoredVoiceExternalObjectMap, createStoredVoiceIntegrationEvent, createStoredVoiceOpsTask, createVoiceFileExternalObjectMapStore, createVoiceFileIntegrationEventStore, createVoiceFileReviewStore, createVoiceFileRuntimeStorage, createVoiceFileSessionStore, createVoiceFileTaskStore, createVoiceFileTraceSinkDeliveryStore, createVoiceFileTraceEventStore } from './fileStore';
|
|
4
5
|
export { buildVoiceTraceReplay, createVoiceMemoryTraceSinkDeliveryStore, createVoiceTraceHTTPSink, createVoiceMemoryTraceEventStore, createVoiceTraceSinkDeliveryId, createVoiceTraceSinkDeliveryRecord, createVoiceTraceSinkStore, createVoiceTraceEvent, createVoiceTraceEventId, deliverVoiceTraceEventsToSinks, evaluateVoiceTrace, exportVoiceTrace, filterVoiceTraceEvents, pruneVoiceTraceEvents, redactVoiceTraceEvent, redactVoiceTraceEvents, redactVoiceTraceText, renderVoiceTraceHTML, renderVoiceTraceMarkdown, resolveVoiceTraceRedactionOptions, selectVoiceTraceEventsForPrune, summarizeVoiceTrace } from './trace';
|
|
@@ -21,6 +22,7 @@ export { conditionAudioChunk, resolveAudioConditioningConfig } from './audioCond
|
|
|
21
22
|
export { resolveVoiceRuntimePreset } from './presets';
|
|
22
23
|
export { resolveTurnDetectionConfig, TURN_PROFILE_DEFAULTS } from './turnProfiles';
|
|
23
24
|
export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewRecorder, renderVoiceCallReviewHTML, renderVoiceCallReviewMarkdown } from './testing/review';
|
|
25
|
+
export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
|
|
24
26
|
export type { VoiceAgent, VoiceAgentMessage, VoiceAgentMessageRole, VoiceAgentModel, VoiceAgentModelInput, VoiceAgentModelOutput, VoiceAgentOptions, VoiceAgentRunResult, VoiceAgentSquadOptions, VoiceAgentTool, VoiceAgentToolCall, VoiceAgentToolResult } from './agent';
|
|
25
27
|
export type { VoiceOpsRuntime, VoiceOpsRuntimeConfig, VoiceOpsRuntimeSummary, VoiceOpsRuntimeSinkWorkerConfig, VoiceOpsRuntimeTaskWorkerConfig, VoiceOpsRuntimeTickResult, VoiceOpsRuntimeWebhookWorkerConfig } from './opsRuntime';
|
|
26
28
|
export type { VoiceOpsPresetName, VoiceOpsPresetOverrides, VoiceResolvedOpsPreset } from './opsPresets';
|
package/dist/index.js
CHANGED
|
@@ -5368,6 +5368,567 @@ var createVoiceAgentSquad = (options) => {
|
|
|
5368
5368
|
run
|
|
5369
5369
|
};
|
|
5370
5370
|
};
|
|
5371
|
+
|
|
5372
|
+
// src/outcomeRecipes.ts
|
|
5373
|
+
var RECIPE_DEFAULTS = {
|
|
5374
|
+
"appointment-booking": {
|
|
5375
|
+
completedAction: "Verify appointment details, confirm calendar state, and send any required confirmation.",
|
|
5376
|
+
completedDescription: "The call completed an appointment-booking flow and should be checked against the scheduling system.",
|
|
5377
|
+
completedKind: "appointment-booking",
|
|
5378
|
+
completedTitle: "Confirm booked appointment",
|
|
5379
|
+
defaultCompletedCreatesTask: true,
|
|
5380
|
+
defaultDueInMs: 30 * 60000,
|
|
5381
|
+
defaultPriority: "normal",
|
|
5382
|
+
defaultQueue: "appointments",
|
|
5383
|
+
description: "Creates appointment confirmation work for completed calls and callback/retry work for missed booking attempts.",
|
|
5384
|
+
escalationQueue: "appointments-escalations"
|
|
5385
|
+
},
|
|
5386
|
+
"lead-qualification": {
|
|
5387
|
+
completedAction: "Review qualification signals, update CRM fields, and route the lead to the right owner.",
|
|
5388
|
+
completedDescription: "The call completed a lead-qualification flow and should be reviewed for sales follow-up.",
|
|
5389
|
+
completedKind: "lead-qualification",
|
|
5390
|
+
completedTitle: "Review qualified lead",
|
|
5391
|
+
defaultCompletedCreatesTask: true,
|
|
5392
|
+
defaultDueInMs: 15 * 60000,
|
|
5393
|
+
defaultPriority: "high",
|
|
5394
|
+
defaultQueue: "sales-leads",
|
|
5395
|
+
description: "Creates sales follow-up work for completed qualification calls and fast callbacks for missed leads.",
|
|
5396
|
+
escalationQueue: "sales-escalations"
|
|
5397
|
+
},
|
|
5398
|
+
"support-triage": {
|
|
5399
|
+
completedAction: "Review the triage result, confirm the support category, and route any unresolved issue.",
|
|
5400
|
+
completedDescription: "The call completed support triage and may need queue routing or human follow-up.",
|
|
5401
|
+
completedKind: "support-triage",
|
|
5402
|
+
completedTitle: "Review support triage",
|
|
5403
|
+
defaultCompletedCreatesTask: true,
|
|
5404
|
+
defaultDueInMs: 20 * 60000,
|
|
5405
|
+
defaultPriority: "normal",
|
|
5406
|
+
defaultQueue: "support-triage",
|
|
5407
|
+
description: "Creates support triage work for completed calls and urgent escalation/callback work for unresolved callers.",
|
|
5408
|
+
escalationQueue: "support-escalations"
|
|
5409
|
+
},
|
|
5410
|
+
"voicemail-callback": {
|
|
5411
|
+
completedAction: "No callback is required for completed calls.",
|
|
5412
|
+
completedDescription: "The call completed without requiring voicemail follow-up.",
|
|
5413
|
+
completedKind: "callback",
|
|
5414
|
+
completedTitle: "Completed call",
|
|
5415
|
+
defaultCompletedCreatesTask: false,
|
|
5416
|
+
defaultDueInMs: 15 * 60000,
|
|
5417
|
+
defaultPriority: "high",
|
|
5418
|
+
defaultQueue: "callbacks",
|
|
5419
|
+
description: "Creates callback work for voicemail, no-answer, failed, or escalated calls while ignoring completed calls.",
|
|
5420
|
+
escalationQueue: "callback-escalations"
|
|
5421
|
+
},
|
|
5422
|
+
"warm-transfer": {
|
|
5423
|
+
completedAction: "Confirm the handoff target received the caller context and close the transfer loop.",
|
|
5424
|
+
completedDescription: "The call is part of a warm-transfer flow and should be verified downstream.",
|
|
5425
|
+
completedKind: "transfer-check",
|
|
5426
|
+
completedTitle: "Verify warm transfer",
|
|
5427
|
+
defaultCompletedCreatesTask: false,
|
|
5428
|
+
defaultDueInMs: 10 * 60000,
|
|
5429
|
+
defaultPriority: "normal",
|
|
5430
|
+
defaultQueue: "transfer-verification",
|
|
5431
|
+
description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
|
|
5432
|
+
escalationQueue: "transfer-escalations"
|
|
5433
|
+
}
|
|
5434
|
+
};
|
|
5435
|
+
var buildRecipeTask = (input) => {
|
|
5436
|
+
const createdAt = input.review.generatedAt ?? Date.now();
|
|
5437
|
+
const queue = input.options.queue ?? input.defaults.defaultQueue;
|
|
5438
|
+
const target = input.options.target ?? input.review.postCall?.target;
|
|
5439
|
+
const common = {
|
|
5440
|
+
assignee: input.options.assignee,
|
|
5441
|
+
createdAt,
|
|
5442
|
+
history: [
|
|
5443
|
+
{
|
|
5444
|
+
actor: "system",
|
|
5445
|
+
at: createdAt,
|
|
5446
|
+
detail: input.review.postCall?.summary,
|
|
5447
|
+
type: "created"
|
|
5448
|
+
}
|
|
5449
|
+
],
|
|
5450
|
+
id: `${input.review.id}:${input.defaults.completedKind}`,
|
|
5451
|
+
intakeId: input.review.id,
|
|
5452
|
+
outcome: input.review.summary.outcome,
|
|
5453
|
+
priority: input.options.priority ?? input.defaults.defaultPriority,
|
|
5454
|
+
queue,
|
|
5455
|
+
reviewId: input.review.id,
|
|
5456
|
+
status: "open",
|
|
5457
|
+
target,
|
|
5458
|
+
updatedAt: createdAt
|
|
5459
|
+
};
|
|
5460
|
+
switch (input.disposition) {
|
|
5461
|
+
case "completed":
|
|
5462
|
+
if (!(input.options.completedCreatesTask ?? input.defaults.defaultCompletedCreatesTask)) {
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
return {
|
|
5466
|
+
...common,
|
|
5467
|
+
description: input.defaults.completedDescription,
|
|
5468
|
+
kind: input.defaults.completedKind,
|
|
5469
|
+
recommendedAction: input.defaults.completedAction,
|
|
5470
|
+
title: target ? `${input.defaults.completedTitle}: ${target}` : input.defaults.completedTitle
|
|
5471
|
+
};
|
|
5472
|
+
case "voicemail":
|
|
5473
|
+
return {
|
|
5474
|
+
...common,
|
|
5475
|
+
description: input.review.postCall?.summary ?? "The caller reached voicemail and needs a callback.",
|
|
5476
|
+
id: `${input.review.id}:callback`,
|
|
5477
|
+
kind: "callback",
|
|
5478
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Call the customer back and continue the original flow.",
|
|
5479
|
+
title: target ? `Call back ${target}` : "Call back voicemail lead"
|
|
5480
|
+
};
|
|
5481
|
+
case "no-answer":
|
|
5482
|
+
return {
|
|
5483
|
+
...common,
|
|
5484
|
+
description: input.review.postCall?.summary ?? "The call did not reach a live respondent and should be retried.",
|
|
5485
|
+
id: `${input.review.id}:retry`,
|
|
5486
|
+
kind: "callback",
|
|
5487
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Retry the call or schedule a callback.",
|
|
5488
|
+
title: "Retry no-answer call"
|
|
5489
|
+
};
|
|
5490
|
+
case "transferred":
|
|
5491
|
+
return {
|
|
5492
|
+
...common,
|
|
5493
|
+
description: input.review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
|
|
5494
|
+
id: `${input.review.id}:transfer-check`,
|
|
5495
|
+
kind: "transfer-check",
|
|
5496
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Confirm the receiving team got the caller context.",
|
|
5497
|
+
title: target ? `Verify transfer to ${target}` : "Verify call transfer"
|
|
5498
|
+
};
|
|
5499
|
+
case "escalated":
|
|
5500
|
+
return {
|
|
5501
|
+
...common,
|
|
5502
|
+
description: input.review.postCall?.summary ?? "The call escalated and needs human review.",
|
|
5503
|
+
id: `${input.review.id}:escalation`,
|
|
5504
|
+
kind: "escalation",
|
|
5505
|
+
priority: "urgent",
|
|
5506
|
+
queue: input.options.escalationQueue ?? input.defaults.escalationQueue,
|
|
5507
|
+
assignee: input.options.escalationAssignee ?? input.options.assignee,
|
|
5508
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Review the escalated call and respond immediately.",
|
|
5509
|
+
title: "Review escalated call"
|
|
5510
|
+
};
|
|
5511
|
+
case "failed":
|
|
5512
|
+
case "closed":
|
|
5513
|
+
return {
|
|
5514
|
+
...common,
|
|
5515
|
+
description: input.review.postCall?.summary ?? "The call ended before successful completion and needs review.",
|
|
5516
|
+
id: `${input.review.id}:retry-review`,
|
|
5517
|
+
kind: "retry-review",
|
|
5518
|
+
priority: "high",
|
|
5519
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Inspect the call and decide whether to retry, escalate, or close.",
|
|
5520
|
+
title: "Inspect incomplete call"
|
|
5521
|
+
};
|
|
5522
|
+
default:
|
|
5523
|
+
return null;
|
|
5524
|
+
}
|
|
5525
|
+
};
|
|
5526
|
+
var resolveVoiceOutcomeRecipe = (name, options = {}) => {
|
|
5527
|
+
const defaults = RECIPE_DEFAULTS[name];
|
|
5528
|
+
const taskPolicies = {
|
|
5529
|
+
completed: {
|
|
5530
|
+
assignee: options.assignee,
|
|
5531
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5532
|
+
name: `${name}-completed`,
|
|
5533
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5534
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5535
|
+
},
|
|
5536
|
+
escalated: {
|
|
5537
|
+
assignee: options.escalationAssignee ?? options.assignee,
|
|
5538
|
+
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 10 * 60000),
|
|
5539
|
+
name: `${name}-escalation`,
|
|
5540
|
+
priority: "urgent",
|
|
5541
|
+
queue: options.escalationQueue ?? defaults.escalationQueue
|
|
5542
|
+
},
|
|
5543
|
+
failed: {
|
|
5544
|
+
assignee: options.assignee,
|
|
5545
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5546
|
+
name: `${name}-failed-review`,
|
|
5547
|
+
priority: "high",
|
|
5548
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5549
|
+
},
|
|
5550
|
+
"no-answer": {
|
|
5551
|
+
assignee: options.assignee,
|
|
5552
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5553
|
+
name: `${name}-no-answer`,
|
|
5554
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5555
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5556
|
+
},
|
|
5557
|
+
transferred: {
|
|
5558
|
+
assignee: options.assignee,
|
|
5559
|
+
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
|
|
5560
|
+
name: `${name}-transfer-check`,
|
|
5561
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5562
|
+
queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
5563
|
+
},
|
|
5564
|
+
voicemail: {
|
|
5565
|
+
assignee: options.assignee,
|
|
5566
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5567
|
+
name: `${name}-voicemail`,
|
|
5568
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5569
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5570
|
+
}
|
|
5571
|
+
};
|
|
5572
|
+
const taskAssignmentRules = [
|
|
5573
|
+
{
|
|
5574
|
+
assign: options.escalationAssignee ?? options.assignee,
|
|
5575
|
+
description: `Route urgent ${name} work to the escalation lane.`,
|
|
5576
|
+
name: `${name}-urgent-routing`,
|
|
5577
|
+
queue: options.escalationQueue ?? defaults.escalationQueue,
|
|
5578
|
+
when: {
|
|
5579
|
+
priority: "urgent"
|
|
5580
|
+
}
|
|
5581
|
+
}
|
|
5582
|
+
].filter((rule) => rule.assign || rule.queue);
|
|
5583
|
+
return {
|
|
5584
|
+
createTaskFromReview: ({ disposition, review }) => buildRecipeTask({
|
|
5585
|
+
defaults,
|
|
5586
|
+
disposition,
|
|
5587
|
+
options,
|
|
5588
|
+
review
|
|
5589
|
+
}),
|
|
5590
|
+
description: defaults.description,
|
|
5591
|
+
name,
|
|
5592
|
+
taskAssignmentRules,
|
|
5593
|
+
taskPolicies
|
|
5594
|
+
};
|
|
5595
|
+
};
|
|
5596
|
+
|
|
5597
|
+
// src/assistant.ts
|
|
5598
|
+
var hashString = (value) => {
|
|
5599
|
+
let hash = 2166136261;
|
|
5600
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
5601
|
+
hash ^= value.charCodeAt(index);
|
|
5602
|
+
hash = Math.imul(hash, 16777619);
|
|
5603
|
+
}
|
|
5604
|
+
return hash >>> 0;
|
|
5605
|
+
};
|
|
5606
|
+
var increment = (record, key) => {
|
|
5607
|
+
record[key] = (record[key] ?? 0) + 1;
|
|
5608
|
+
};
|
|
5609
|
+
var resolveOutcome = (result) => {
|
|
5610
|
+
if (result.transfer) {
|
|
5611
|
+
return "transferred";
|
|
5612
|
+
}
|
|
5613
|
+
if (result.escalate) {
|
|
5614
|
+
return "escalated";
|
|
5615
|
+
}
|
|
5616
|
+
if (result.voicemail) {
|
|
5617
|
+
return "voicemail";
|
|
5618
|
+
}
|
|
5619
|
+
if (result.noAnswer) {
|
|
5620
|
+
return "no-answer";
|
|
5621
|
+
}
|
|
5622
|
+
if (result.complete) {
|
|
5623
|
+
return "completed";
|
|
5624
|
+
}
|
|
5625
|
+
return "continued";
|
|
5626
|
+
};
|
|
5627
|
+
var resolveArtifactPlanName = (artifactPlan) => {
|
|
5628
|
+
const preset = artifactPlan?.preset;
|
|
5629
|
+
if (!preset) {
|
|
5630
|
+
return artifactPlan?.ops ? "custom" : undefined;
|
|
5631
|
+
}
|
|
5632
|
+
return typeof preset === "string" ? preset : preset.name;
|
|
5633
|
+
};
|
|
5634
|
+
var appendAssistantTrace = async (input) => {
|
|
5635
|
+
await input.trace?.append({
|
|
5636
|
+
at: Date.now(),
|
|
5637
|
+
payload: {
|
|
5638
|
+
assistantId: input.assistantId,
|
|
5639
|
+
...input.event
|
|
5640
|
+
},
|
|
5641
|
+
scenarioId: input.session.scenarioId,
|
|
5642
|
+
sessionId: input.session.id,
|
|
5643
|
+
turnId: input.turnId,
|
|
5644
|
+
type: input.type
|
|
5645
|
+
});
|
|
5646
|
+
};
|
|
5647
|
+
var resolvePresetOps = (artifactPlan) => {
|
|
5648
|
+
const preset = artifactPlan?.preset;
|
|
5649
|
+
if (!preset) {
|
|
5650
|
+
return artifactPlan?.ops;
|
|
5651
|
+
}
|
|
5652
|
+
const recipe = typeof preset === "string" ? resolveVoiceOutcomeRecipe(preset) : resolveVoiceOutcomeRecipe(preset.name, preset.options);
|
|
5653
|
+
return {
|
|
5654
|
+
...recipe,
|
|
5655
|
+
...artifactPlan?.ops
|
|
5656
|
+
};
|
|
5657
|
+
};
|
|
5658
|
+
var mergeOps = (base, override) => {
|
|
5659
|
+
if (!base && !override) {
|
|
5660
|
+
return;
|
|
5661
|
+
}
|
|
5662
|
+
return {
|
|
5663
|
+
...base,
|
|
5664
|
+
...override,
|
|
5665
|
+
taskAssignmentRules: base?.taskAssignmentRules || override?.taskAssignmentRules ? [
|
|
5666
|
+
...base?.taskAssignmentRules ?? [],
|
|
5667
|
+
...override?.taskAssignmentRules ?? []
|
|
5668
|
+
] : undefined,
|
|
5669
|
+
taskPolicies: base?.taskPolicies || override?.taskPolicies ? {
|
|
5670
|
+
...base?.taskPolicies ?? {},
|
|
5671
|
+
...override?.taskPolicies ?? {}
|
|
5672
|
+
} : undefined
|
|
5673
|
+
};
|
|
5674
|
+
};
|
|
5675
|
+
var createVoiceExperiment = (options) => {
|
|
5676
|
+
if (!options.variants.length) {
|
|
5677
|
+
throw new Error("createVoiceExperiment requires at least one variant.");
|
|
5678
|
+
}
|
|
5679
|
+
const firstVariant = options.variants[0];
|
|
5680
|
+
return {
|
|
5681
|
+
id: options.id,
|
|
5682
|
+
resolve: (input) => {
|
|
5683
|
+
const selected = options.selectVariant?.({
|
|
5684
|
+
...input,
|
|
5685
|
+
variants: options.variants
|
|
5686
|
+
});
|
|
5687
|
+
if (selected && typeof selected !== "object") {
|
|
5688
|
+
const variant = options.variants.find((item) => item.id === selected);
|
|
5689
|
+
if (variant) {
|
|
5690
|
+
return variant;
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
if (selected && typeof selected === "object" && "id" in selected) {
|
|
5694
|
+
return selected;
|
|
5695
|
+
}
|
|
5696
|
+
const totalWeight = options.variants.reduce((total, variant) => total + Math.max(0, variant.weight ?? 1), 0);
|
|
5697
|
+
if (totalWeight <= 0) {
|
|
5698
|
+
return firstVariant;
|
|
5699
|
+
}
|
|
5700
|
+
const bucket = hashString(`${options.id}:${input.assistantId}:${input.session.id}`) % totalWeight;
|
|
5701
|
+
let cursor = 0;
|
|
5702
|
+
for (const variant of options.variants) {
|
|
5703
|
+
cursor += Math.max(0, variant.weight ?? 1);
|
|
5704
|
+
if (bucket < cursor) {
|
|
5705
|
+
return variant;
|
|
5706
|
+
}
|
|
5707
|
+
}
|
|
5708
|
+
return firstVariant;
|
|
5709
|
+
},
|
|
5710
|
+
variants: options.variants
|
|
5711
|
+
};
|
|
5712
|
+
};
|
|
5713
|
+
var createVoiceAssistant = (options) => {
|
|
5714
|
+
const ops = mergeOps(resolvePresetOps(options.artifactPlan), options.ops);
|
|
5715
|
+
const artifactPlanName = resolveArtifactPlanName(options.artifactPlan);
|
|
5716
|
+
let agent;
|
|
5717
|
+
const baseModelOptions = "model" in options && options.model ? {
|
|
5718
|
+
maxToolRounds: options.maxToolRounds,
|
|
5719
|
+
model: options.model,
|
|
5720
|
+
system: options.system,
|
|
5721
|
+
tools: options.tools
|
|
5722
|
+
} : undefined;
|
|
5723
|
+
if ("agent" in options && options.agent) {
|
|
5724
|
+
agent = options.agent;
|
|
5725
|
+
} else if ("agents" in options && options.agents) {
|
|
5726
|
+
agent = createVoiceAgentSquad({
|
|
5727
|
+
agents: options.agents,
|
|
5728
|
+
defaultAgentId: options.defaultAgentId,
|
|
5729
|
+
id: options.id,
|
|
5730
|
+
maxHandoffsPerTurn: options.maxHandoffsPerTurn,
|
|
5731
|
+
selectAgent: options.selectAgent,
|
|
5732
|
+
trace: options.trace
|
|
5733
|
+
});
|
|
5734
|
+
} else {
|
|
5735
|
+
agent = createVoiceAgent({
|
|
5736
|
+
id: options.id,
|
|
5737
|
+
maxToolRounds: options.maxToolRounds,
|
|
5738
|
+
model: options.model,
|
|
5739
|
+
system: options.system,
|
|
5740
|
+
trace: options.trace,
|
|
5741
|
+
tools: options.tools
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
const onTurn = async (input) => {
|
|
5745
|
+
const guardrailInput = {
|
|
5746
|
+
...input,
|
|
5747
|
+
assistantId: options.id
|
|
5748
|
+
};
|
|
5749
|
+
const blocked = await options.guardrails?.beforeTurn?.(guardrailInput);
|
|
5750
|
+
if (blocked) {
|
|
5751
|
+
await appendAssistantTrace({
|
|
5752
|
+
assistantId: options.id,
|
|
5753
|
+
event: {
|
|
5754
|
+
action: "blocked",
|
|
5755
|
+
artifactPlan: artifactPlanName,
|
|
5756
|
+
outcome: resolveOutcome(blocked)
|
|
5757
|
+
},
|
|
5758
|
+
session: input.session,
|
|
5759
|
+
trace: options.trace,
|
|
5760
|
+
turnId: input.turn.id,
|
|
5761
|
+
type: "assistant.guardrail"
|
|
5762
|
+
});
|
|
5763
|
+
await appendAssistantTrace({
|
|
5764
|
+
assistantId: options.id,
|
|
5765
|
+
event: {
|
|
5766
|
+
artifactPlan: artifactPlanName,
|
|
5767
|
+
blocked: true,
|
|
5768
|
+
experimentId: options.experiment?.id,
|
|
5769
|
+
outcome: resolveOutcome(blocked)
|
|
5770
|
+
},
|
|
5771
|
+
session: input.session,
|
|
5772
|
+
trace: options.trace,
|
|
5773
|
+
turnId: input.turn.id,
|
|
5774
|
+
type: "assistant.run"
|
|
5775
|
+
});
|
|
5776
|
+
return blocked;
|
|
5777
|
+
}
|
|
5778
|
+
const startedAt = Date.now();
|
|
5779
|
+
const variant = options.experiment?.resolve({
|
|
5780
|
+
assistantId: options.id,
|
|
5781
|
+
context: input.context,
|
|
5782
|
+
session: input.session,
|
|
5783
|
+
turnId: input.turn.id
|
|
5784
|
+
});
|
|
5785
|
+
const runner = variant && baseModelOptions ? createVoiceAgent({
|
|
5786
|
+
id: `${options.id}:${variant.id}`,
|
|
5787
|
+
maxToolRounds: variant.maxToolRounds ?? baseModelOptions.maxToolRounds,
|
|
5788
|
+
model: variant.model ?? baseModelOptions.model,
|
|
5789
|
+
system: variant.system ?? baseModelOptions.system,
|
|
5790
|
+
trace: options.trace,
|
|
5791
|
+
tools: variant.tools ?? baseModelOptions.tools
|
|
5792
|
+
}) : agent;
|
|
5793
|
+
const runResult = await runner.run(input) ?? {};
|
|
5794
|
+
const result = runResult;
|
|
5795
|
+
const guarded = await options.guardrails?.afterTurn?.({
|
|
5796
|
+
...guardrailInput,
|
|
5797
|
+
result
|
|
5798
|
+
});
|
|
5799
|
+
const finalResult = guarded ?? result;
|
|
5800
|
+
if (guarded) {
|
|
5801
|
+
await appendAssistantTrace({
|
|
5802
|
+
assistantId: options.id,
|
|
5803
|
+
event: {
|
|
5804
|
+
action: "rewritten",
|
|
5805
|
+
artifactPlan: artifactPlanName,
|
|
5806
|
+
experimentId: options.experiment?.id,
|
|
5807
|
+
outcome: resolveOutcome(finalResult),
|
|
5808
|
+
variantId: variant?.id
|
|
5809
|
+
},
|
|
5810
|
+
session: input.session,
|
|
5811
|
+
trace: options.trace,
|
|
5812
|
+
turnId: input.turn.id,
|
|
5813
|
+
type: "assistant.guardrail"
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
await appendAssistantTrace({
|
|
5817
|
+
assistantId: options.id,
|
|
5818
|
+
event: {
|
|
5819
|
+
artifactPlan: artifactPlanName,
|
|
5820
|
+
blocked: false,
|
|
5821
|
+
elapsedMs: Date.now() - startedAt,
|
|
5822
|
+
escalated: Boolean(finalResult.escalate),
|
|
5823
|
+
experimentId: options.experiment?.id,
|
|
5824
|
+
outcome: resolveOutcome(finalResult),
|
|
5825
|
+
toolNames: result.toolResults?.map((tool) => tool.toolName) ?? [],
|
|
5826
|
+
transferred: Boolean(finalResult.transfer),
|
|
5827
|
+
variantId: variant?.id
|
|
5828
|
+
},
|
|
5829
|
+
session: input.session,
|
|
5830
|
+
trace: options.trace,
|
|
5831
|
+
turnId: input.turn.id,
|
|
5832
|
+
type: "assistant.run"
|
|
5833
|
+
});
|
|
5834
|
+
return finalResult;
|
|
5835
|
+
};
|
|
5836
|
+
return {
|
|
5837
|
+
agent,
|
|
5838
|
+
id: options.id,
|
|
5839
|
+
onTurn,
|
|
5840
|
+
ops,
|
|
5841
|
+
route: (overrides) => ({
|
|
5842
|
+
...overrides,
|
|
5843
|
+
onComplete: overrides.onComplete ?? (() => {
|
|
5844
|
+
return;
|
|
5845
|
+
}),
|
|
5846
|
+
onTurn
|
|
5847
|
+
})
|
|
5848
|
+
};
|
|
5849
|
+
};
|
|
5850
|
+
var summarizeVoiceAssistantRuns = async (input) => {
|
|
5851
|
+
const events = Array.isArray(input) ? input : input.events ?? await input.store?.list() ?? [];
|
|
5852
|
+
const assistantRuns = events.filter((event) => event.type === "assistant.run");
|
|
5853
|
+
const guardrails = events.filter((event) => event.type === "assistant.guardrail");
|
|
5854
|
+
const byAssistant = new Map;
|
|
5855
|
+
const getSummary = (assistantId) => {
|
|
5856
|
+
let summary = byAssistant.get(assistantId);
|
|
5857
|
+
if (!summary) {
|
|
5858
|
+
summary = {
|
|
5859
|
+
assistantId,
|
|
5860
|
+
artifactPlans: {},
|
|
5861
|
+
blockedGuardrailCount: 0,
|
|
5862
|
+
elapsedCount: 0,
|
|
5863
|
+
elapsedTotal: 0,
|
|
5864
|
+
escalationCount: 0,
|
|
5865
|
+
experiments: {},
|
|
5866
|
+
guardrailCount: 0,
|
|
5867
|
+
outcomes: {},
|
|
5868
|
+
runCount: 0,
|
|
5869
|
+
sessionIds: new Set,
|
|
5870
|
+
sessions: 0,
|
|
5871
|
+
toolCalls: {},
|
|
5872
|
+
transferCount: 0,
|
|
5873
|
+
variants: {}
|
|
5874
|
+
};
|
|
5875
|
+
byAssistant.set(assistantId, summary);
|
|
5876
|
+
}
|
|
5877
|
+
return summary;
|
|
5878
|
+
};
|
|
5879
|
+
for (const event of assistantRuns) {
|
|
5880
|
+
const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
|
|
5881
|
+
const summary = getSummary(assistantId);
|
|
5882
|
+
summary.runCount += 1;
|
|
5883
|
+
summary.sessionIds.add(event.sessionId);
|
|
5884
|
+
if (typeof event.payload.artifactPlan === "string") {
|
|
5885
|
+
increment(summary.artifactPlans, event.payload.artifactPlan);
|
|
5886
|
+
}
|
|
5887
|
+
if (typeof event.payload.experimentId === "string") {
|
|
5888
|
+
increment(summary.experiments, event.payload.experimentId);
|
|
5889
|
+
}
|
|
5890
|
+
if (typeof event.payload.variantId === "string") {
|
|
5891
|
+
increment(summary.variants, event.payload.variantId);
|
|
5892
|
+
}
|
|
5893
|
+
if (typeof event.payload.outcome === "string") {
|
|
5894
|
+
increment(summary.outcomes, event.payload.outcome);
|
|
5895
|
+
}
|
|
5896
|
+
if (event.payload.escalated === true) {
|
|
5897
|
+
summary.escalationCount += 1;
|
|
5898
|
+
}
|
|
5899
|
+
if (event.payload.transferred === true) {
|
|
5900
|
+
summary.transferCount += 1;
|
|
5901
|
+
}
|
|
5902
|
+
if (event.payload.blocked === true) {
|
|
5903
|
+
summary.blockedGuardrailCount += 1;
|
|
5904
|
+
}
|
|
5905
|
+
if (typeof event.payload.elapsedMs === "number") {
|
|
5906
|
+
summary.elapsedCount += 1;
|
|
5907
|
+
summary.elapsedTotal += event.payload.elapsedMs;
|
|
5908
|
+
}
|
|
5909
|
+
if (Array.isArray(event.payload.toolNames)) {
|
|
5910
|
+
for (const toolName of event.payload.toolNames) {
|
|
5911
|
+
if (typeof toolName === "string") {
|
|
5912
|
+
increment(summary.toolCalls, toolName);
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
for (const event of guardrails) {
|
|
5918
|
+
const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
|
|
5919
|
+
const summary = getSummary(assistantId);
|
|
5920
|
+
summary.guardrailCount += 1;
|
|
5921
|
+
}
|
|
5922
|
+
const assistants = [...byAssistant.values()].map(({ elapsedCount, elapsedTotal, sessionIds, ...summary }) => ({
|
|
5923
|
+
...summary,
|
|
5924
|
+
averageElapsedMs: elapsedCount > 0 ? Math.round(elapsedTotal / elapsedCount) : undefined,
|
|
5925
|
+
sessions: sessionIds.size
|
|
5926
|
+
}));
|
|
5927
|
+
return {
|
|
5928
|
+
assistants: assistants.sort((left, right) => left.assistantId.localeCompare(right.assistantId)),
|
|
5929
|
+
totalRuns: assistantRuns.length
|
|
5930
|
+
};
|
|
5931
|
+
};
|
|
5371
5932
|
// src/fileStore.ts
|
|
5372
5933
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
5373
5934
|
import { join } from "path";
|
|
@@ -7987,230 +8548,6 @@ var resolveVoiceOpsPreset = (name, overrides = {}) => {
|
|
|
7987
8548
|
taskPolicies: mergePolicies(preset.taskPolicies, overrides.taskPolicies)
|
|
7988
8549
|
};
|
|
7989
8550
|
};
|
|
7990
|
-
// src/outcomeRecipes.ts
|
|
7991
|
-
var RECIPE_DEFAULTS = {
|
|
7992
|
-
"appointment-booking": {
|
|
7993
|
-
completedAction: "Verify appointment details, confirm calendar state, and send any required confirmation.",
|
|
7994
|
-
completedDescription: "The call completed an appointment-booking flow and should be checked against the scheduling system.",
|
|
7995
|
-
completedKind: "appointment-booking",
|
|
7996
|
-
completedTitle: "Confirm booked appointment",
|
|
7997
|
-
defaultCompletedCreatesTask: true,
|
|
7998
|
-
defaultDueInMs: 30 * 60000,
|
|
7999
|
-
defaultPriority: "normal",
|
|
8000
|
-
defaultQueue: "appointments",
|
|
8001
|
-
description: "Creates appointment confirmation work for completed calls and callback/retry work for missed booking attempts.",
|
|
8002
|
-
escalationQueue: "appointments-escalations"
|
|
8003
|
-
},
|
|
8004
|
-
"lead-qualification": {
|
|
8005
|
-
completedAction: "Review qualification signals, update CRM fields, and route the lead to the right owner.",
|
|
8006
|
-
completedDescription: "The call completed a lead-qualification flow and should be reviewed for sales follow-up.",
|
|
8007
|
-
completedKind: "lead-qualification",
|
|
8008
|
-
completedTitle: "Review qualified lead",
|
|
8009
|
-
defaultCompletedCreatesTask: true,
|
|
8010
|
-
defaultDueInMs: 15 * 60000,
|
|
8011
|
-
defaultPriority: "high",
|
|
8012
|
-
defaultQueue: "sales-leads",
|
|
8013
|
-
description: "Creates sales follow-up work for completed qualification calls and fast callbacks for missed leads.",
|
|
8014
|
-
escalationQueue: "sales-escalations"
|
|
8015
|
-
},
|
|
8016
|
-
"support-triage": {
|
|
8017
|
-
completedAction: "Review the triage result, confirm the support category, and route any unresolved issue.",
|
|
8018
|
-
completedDescription: "The call completed support triage and may need queue routing or human follow-up.",
|
|
8019
|
-
completedKind: "support-triage",
|
|
8020
|
-
completedTitle: "Review support triage",
|
|
8021
|
-
defaultCompletedCreatesTask: true,
|
|
8022
|
-
defaultDueInMs: 20 * 60000,
|
|
8023
|
-
defaultPriority: "normal",
|
|
8024
|
-
defaultQueue: "support-triage",
|
|
8025
|
-
description: "Creates support triage work for completed calls and urgent escalation/callback work for unresolved callers.",
|
|
8026
|
-
escalationQueue: "support-escalations"
|
|
8027
|
-
},
|
|
8028
|
-
"voicemail-callback": {
|
|
8029
|
-
completedAction: "No callback is required for completed calls.",
|
|
8030
|
-
completedDescription: "The call completed without requiring voicemail follow-up.",
|
|
8031
|
-
completedKind: "callback",
|
|
8032
|
-
completedTitle: "Completed call",
|
|
8033
|
-
defaultCompletedCreatesTask: false,
|
|
8034
|
-
defaultDueInMs: 15 * 60000,
|
|
8035
|
-
defaultPriority: "high",
|
|
8036
|
-
defaultQueue: "callbacks",
|
|
8037
|
-
description: "Creates callback work for voicemail, no-answer, failed, or escalated calls while ignoring completed calls.",
|
|
8038
|
-
escalationQueue: "callback-escalations"
|
|
8039
|
-
},
|
|
8040
|
-
"warm-transfer": {
|
|
8041
|
-
completedAction: "Confirm the handoff target received the caller context and close the transfer loop.",
|
|
8042
|
-
completedDescription: "The call is part of a warm-transfer flow and should be verified downstream.",
|
|
8043
|
-
completedKind: "transfer-check",
|
|
8044
|
-
completedTitle: "Verify warm transfer",
|
|
8045
|
-
defaultCompletedCreatesTask: false,
|
|
8046
|
-
defaultDueInMs: 10 * 60000,
|
|
8047
|
-
defaultPriority: "normal",
|
|
8048
|
-
defaultQueue: "transfer-verification",
|
|
8049
|
-
description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
|
|
8050
|
-
escalationQueue: "transfer-escalations"
|
|
8051
|
-
}
|
|
8052
|
-
};
|
|
8053
|
-
var buildRecipeTask = (input) => {
|
|
8054
|
-
const createdAt = input.review.generatedAt ?? Date.now();
|
|
8055
|
-
const queue = input.options.queue ?? input.defaults.defaultQueue;
|
|
8056
|
-
const target = input.options.target ?? input.review.postCall?.target;
|
|
8057
|
-
const common = {
|
|
8058
|
-
assignee: input.options.assignee,
|
|
8059
|
-
createdAt,
|
|
8060
|
-
history: [
|
|
8061
|
-
{
|
|
8062
|
-
actor: "system",
|
|
8063
|
-
at: createdAt,
|
|
8064
|
-
detail: input.review.postCall?.summary,
|
|
8065
|
-
type: "created"
|
|
8066
|
-
}
|
|
8067
|
-
],
|
|
8068
|
-
id: `${input.review.id}:${input.defaults.completedKind}`,
|
|
8069
|
-
intakeId: input.review.id,
|
|
8070
|
-
outcome: input.review.summary.outcome,
|
|
8071
|
-
priority: input.options.priority ?? input.defaults.defaultPriority,
|
|
8072
|
-
queue,
|
|
8073
|
-
reviewId: input.review.id,
|
|
8074
|
-
status: "open",
|
|
8075
|
-
target,
|
|
8076
|
-
updatedAt: createdAt
|
|
8077
|
-
};
|
|
8078
|
-
switch (input.disposition) {
|
|
8079
|
-
case "completed":
|
|
8080
|
-
if (!(input.options.completedCreatesTask ?? input.defaults.defaultCompletedCreatesTask)) {
|
|
8081
|
-
return null;
|
|
8082
|
-
}
|
|
8083
|
-
return {
|
|
8084
|
-
...common,
|
|
8085
|
-
description: input.defaults.completedDescription,
|
|
8086
|
-
kind: input.defaults.completedKind,
|
|
8087
|
-
recommendedAction: input.defaults.completedAction,
|
|
8088
|
-
title: target ? `${input.defaults.completedTitle}: ${target}` : input.defaults.completedTitle
|
|
8089
|
-
};
|
|
8090
|
-
case "voicemail":
|
|
8091
|
-
return {
|
|
8092
|
-
...common,
|
|
8093
|
-
description: input.review.postCall?.summary ?? "The caller reached voicemail and needs a callback.",
|
|
8094
|
-
id: `${input.review.id}:callback`,
|
|
8095
|
-
kind: "callback",
|
|
8096
|
-
recommendedAction: input.review.postCall?.recommendedAction ?? "Call the customer back and continue the original flow.",
|
|
8097
|
-
title: target ? `Call back ${target}` : "Call back voicemail lead"
|
|
8098
|
-
};
|
|
8099
|
-
case "no-answer":
|
|
8100
|
-
return {
|
|
8101
|
-
...common,
|
|
8102
|
-
description: input.review.postCall?.summary ?? "The call did not reach a live respondent and should be retried.",
|
|
8103
|
-
id: `${input.review.id}:retry`,
|
|
8104
|
-
kind: "callback",
|
|
8105
|
-
recommendedAction: input.review.postCall?.recommendedAction ?? "Retry the call or schedule a callback.",
|
|
8106
|
-
title: "Retry no-answer call"
|
|
8107
|
-
};
|
|
8108
|
-
case "transferred":
|
|
8109
|
-
return {
|
|
8110
|
-
...common,
|
|
8111
|
-
description: input.review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
|
|
8112
|
-
id: `${input.review.id}:transfer-check`,
|
|
8113
|
-
kind: "transfer-check",
|
|
8114
|
-
recommendedAction: input.review.postCall?.recommendedAction ?? "Confirm the receiving team got the caller context.",
|
|
8115
|
-
title: target ? `Verify transfer to ${target}` : "Verify call transfer"
|
|
8116
|
-
};
|
|
8117
|
-
case "escalated":
|
|
8118
|
-
return {
|
|
8119
|
-
...common,
|
|
8120
|
-
description: input.review.postCall?.summary ?? "The call escalated and needs human review.",
|
|
8121
|
-
id: `${input.review.id}:escalation`,
|
|
8122
|
-
kind: "escalation",
|
|
8123
|
-
priority: "urgent",
|
|
8124
|
-
queue: input.options.escalationQueue ?? input.defaults.escalationQueue,
|
|
8125
|
-
assignee: input.options.escalationAssignee ?? input.options.assignee,
|
|
8126
|
-
recommendedAction: input.review.postCall?.recommendedAction ?? "Review the escalated call and respond immediately.",
|
|
8127
|
-
title: "Review escalated call"
|
|
8128
|
-
};
|
|
8129
|
-
case "failed":
|
|
8130
|
-
case "closed":
|
|
8131
|
-
return {
|
|
8132
|
-
...common,
|
|
8133
|
-
description: input.review.postCall?.summary ?? "The call ended before successful completion and needs review.",
|
|
8134
|
-
id: `${input.review.id}:retry-review`,
|
|
8135
|
-
kind: "retry-review",
|
|
8136
|
-
priority: "high",
|
|
8137
|
-
recommendedAction: input.review.postCall?.recommendedAction ?? "Inspect the call and decide whether to retry, escalate, or close.",
|
|
8138
|
-
title: "Inspect incomplete call"
|
|
8139
|
-
};
|
|
8140
|
-
default:
|
|
8141
|
-
return null;
|
|
8142
|
-
}
|
|
8143
|
-
};
|
|
8144
|
-
var resolveVoiceOutcomeRecipe = (name, options = {}) => {
|
|
8145
|
-
const defaults = RECIPE_DEFAULTS[name];
|
|
8146
|
-
const taskPolicies = {
|
|
8147
|
-
completed: {
|
|
8148
|
-
assignee: options.assignee,
|
|
8149
|
-
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
8150
|
-
name: `${name}-completed`,
|
|
8151
|
-
priority: options.priority ?? defaults.defaultPriority,
|
|
8152
|
-
queue: options.queue ?? defaults.defaultQueue
|
|
8153
|
-
},
|
|
8154
|
-
escalated: {
|
|
8155
|
-
assignee: options.escalationAssignee ?? options.assignee,
|
|
8156
|
-
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 10 * 60000),
|
|
8157
|
-
name: `${name}-escalation`,
|
|
8158
|
-
priority: "urgent",
|
|
8159
|
-
queue: options.escalationQueue ?? defaults.escalationQueue
|
|
8160
|
-
},
|
|
8161
|
-
failed: {
|
|
8162
|
-
assignee: options.assignee,
|
|
8163
|
-
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
8164
|
-
name: `${name}-failed-review`,
|
|
8165
|
-
priority: "high",
|
|
8166
|
-
queue: options.queue ?? defaults.defaultQueue
|
|
8167
|
-
},
|
|
8168
|
-
"no-answer": {
|
|
8169
|
-
assignee: options.assignee,
|
|
8170
|
-
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
8171
|
-
name: `${name}-no-answer`,
|
|
8172
|
-
priority: options.priority ?? defaults.defaultPriority,
|
|
8173
|
-
queue: options.queue ?? defaults.defaultQueue
|
|
8174
|
-
},
|
|
8175
|
-
transferred: {
|
|
8176
|
-
assignee: options.assignee,
|
|
8177
|
-
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
|
|
8178
|
-
name: `${name}-transfer-check`,
|
|
8179
|
-
priority: options.priority ?? defaults.defaultPriority,
|
|
8180
|
-
queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
8181
|
-
},
|
|
8182
|
-
voicemail: {
|
|
8183
|
-
assignee: options.assignee,
|
|
8184
|
-
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
8185
|
-
name: `${name}-voicemail`,
|
|
8186
|
-
priority: options.priority ?? defaults.defaultPriority,
|
|
8187
|
-
queue: options.queue ?? defaults.defaultQueue
|
|
8188
|
-
}
|
|
8189
|
-
};
|
|
8190
|
-
const taskAssignmentRules = [
|
|
8191
|
-
{
|
|
8192
|
-
assign: options.escalationAssignee ?? options.assignee,
|
|
8193
|
-
description: `Route urgent ${name} work to the escalation lane.`,
|
|
8194
|
-
name: `${name}-urgent-routing`,
|
|
8195
|
-
queue: options.escalationQueue ?? defaults.escalationQueue,
|
|
8196
|
-
when: {
|
|
8197
|
-
priority: "urgent"
|
|
8198
|
-
}
|
|
8199
|
-
}
|
|
8200
|
-
].filter((rule) => rule.assign || rule.queue);
|
|
8201
|
-
return {
|
|
8202
|
-
createTaskFromReview: ({ disposition, review }) => buildRecipeTask({
|
|
8203
|
-
defaults,
|
|
8204
|
-
disposition,
|
|
8205
|
-
options,
|
|
8206
|
-
review
|
|
8207
|
-
}),
|
|
8208
|
-
description: defaults.description,
|
|
8209
|
-
name,
|
|
8210
|
-
taskAssignmentRules,
|
|
8211
|
-
taskPolicies
|
|
8212
|
-
};
|
|
8213
|
-
};
|
|
8214
8551
|
// src/correction.ts
|
|
8215
8552
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8216
8553
|
var buildAliasMatcher = (alias) => new RegExp(`(?<![\\p{L}\\p{N}'])${escapeRegExp(alias)}(?![\\p{L}\\p{N}'])`, "giu");
|
|
@@ -8990,6 +9327,7 @@ export {
|
|
|
8990
9327
|
summarizeVoiceOpsTaskQueue,
|
|
8991
9328
|
summarizeVoiceOpsTaskAnalytics,
|
|
8992
9329
|
summarizeVoiceIntegrationEvents,
|
|
9330
|
+
summarizeVoiceAssistantRuns,
|
|
8993
9331
|
startVoiceOpsTask,
|
|
8994
9332
|
shapeTelephonyAssistantText,
|
|
8995
9333
|
selectVoiceTraceEventsForPrune,
|
|
@@ -9097,11 +9435,13 @@ export {
|
|
|
9097
9435
|
createVoiceFileExternalObjectMapStore,
|
|
9098
9436
|
createVoiceExternalObjectMapId,
|
|
9099
9437
|
createVoiceExternalObjectMap,
|
|
9438
|
+
createVoiceExperiment,
|
|
9100
9439
|
createVoiceCallReviewRecorder,
|
|
9101
9440
|
createVoiceCallReviewFromSession,
|
|
9102
9441
|
createVoiceCallReviewFromLiveTelephonyReport,
|
|
9103
9442
|
createVoiceCallCompletedEvent,
|
|
9104
9443
|
createVoiceCRMActivitySink,
|
|
9444
|
+
createVoiceAssistant,
|
|
9105
9445
|
createVoiceAgentTool,
|
|
9106
9446
|
createVoiceAgentSquad,
|
|
9107
9447
|
createVoiceAgent,
|
package/dist/trace.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type VoiceTraceEventType = 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.lifecycle' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript';
|
|
1
|
+
export type VoiceTraceEventType = 'assistant.guardrail' | 'assistant.run' | 'agent.handoff' | 'agent.model' | 'agent.result' | 'agent.tool' | 'call.lifecycle' | 'session.error' | 'turn.assistant' | 'turn.committed' | 'turn.cost' | 'turn.transcript';
|
|
2
2
|
export type VoiceTraceEvent<TPayload extends Record<string, unknown> = Record<string, unknown>> = {
|
|
3
3
|
at: number;
|
|
4
4
|
id?: string;
|