@absolutejs/voice 0.0.22-beta.1 → 0.0.22-beta.10
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 +143 -0
- package/dist/assistantMemory.d.ts +63 -0
- package/dist/fileStore.d.ts +5 -2
- package/dist/index.d.ts +7 -1
- package/dist/index.js +1380 -233
- package/dist/modelAdapters.d.ts +40 -0
- package/dist/trace.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5150,6 +5150,17 @@ var createVoiceAgent = (options) => {
|
|
|
5150
5150
|
if (output.assistantText?.trim()) {
|
|
5151
5151
|
messages.push({
|
|
5152
5152
|
content: output.assistantText,
|
|
5153
|
+
metadata: output.toolCalls?.length ? {
|
|
5154
|
+
toolCalls: output.toolCalls
|
|
5155
|
+
} : undefined,
|
|
5156
|
+
role: "assistant"
|
|
5157
|
+
});
|
|
5158
|
+
} else if (output.toolCalls?.length) {
|
|
5159
|
+
messages.push({
|
|
5160
|
+
content: "",
|
|
5161
|
+
metadata: {
|
|
5162
|
+
toolCalls: output.toolCalls
|
|
5163
|
+
},
|
|
5153
5164
|
role: "assistant"
|
|
5154
5165
|
});
|
|
5155
5166
|
}
|
|
@@ -5352,20 +5363,742 @@ var createVoiceAgentSquad = (options) => {
|
|
|
5352
5363
|
agentId = nextAgent.id;
|
|
5353
5364
|
result = await agent.run({
|
|
5354
5365
|
...input,
|
|
5355
|
-
messages
|
|
5366
|
+
messages
|
|
5367
|
+
});
|
|
5368
|
+
toolResults.push(...result.toolResults);
|
|
5369
|
+
}
|
|
5370
|
+
return {
|
|
5371
|
+
...result,
|
|
5372
|
+
agentId,
|
|
5373
|
+
toolResults
|
|
5374
|
+
};
|
|
5375
|
+
};
|
|
5376
|
+
return {
|
|
5377
|
+
id: options.id,
|
|
5378
|
+
onTurn: async (input) => run(input),
|
|
5379
|
+
run
|
|
5380
|
+
};
|
|
5381
|
+
};
|
|
5382
|
+
|
|
5383
|
+
// src/outcomeRecipes.ts
|
|
5384
|
+
var RECIPE_DEFAULTS = {
|
|
5385
|
+
"appointment-booking": {
|
|
5386
|
+
completedAction: "Verify appointment details, confirm calendar state, and send any required confirmation.",
|
|
5387
|
+
completedDescription: "The call completed an appointment-booking flow and should be checked against the scheduling system.",
|
|
5388
|
+
completedKind: "appointment-booking",
|
|
5389
|
+
completedTitle: "Confirm booked appointment",
|
|
5390
|
+
defaultCompletedCreatesTask: true,
|
|
5391
|
+
defaultDueInMs: 30 * 60000,
|
|
5392
|
+
defaultPriority: "normal",
|
|
5393
|
+
defaultQueue: "appointments",
|
|
5394
|
+
description: "Creates appointment confirmation work for completed calls and callback/retry work for missed booking attempts.",
|
|
5395
|
+
escalationQueue: "appointments-escalations"
|
|
5396
|
+
},
|
|
5397
|
+
"lead-qualification": {
|
|
5398
|
+
completedAction: "Review qualification signals, update CRM fields, and route the lead to the right owner.",
|
|
5399
|
+
completedDescription: "The call completed a lead-qualification flow and should be reviewed for sales follow-up.",
|
|
5400
|
+
completedKind: "lead-qualification",
|
|
5401
|
+
completedTitle: "Review qualified lead",
|
|
5402
|
+
defaultCompletedCreatesTask: true,
|
|
5403
|
+
defaultDueInMs: 15 * 60000,
|
|
5404
|
+
defaultPriority: "high",
|
|
5405
|
+
defaultQueue: "sales-leads",
|
|
5406
|
+
description: "Creates sales follow-up work for completed qualification calls and fast callbacks for missed leads.",
|
|
5407
|
+
escalationQueue: "sales-escalations"
|
|
5408
|
+
},
|
|
5409
|
+
"support-triage": {
|
|
5410
|
+
completedAction: "Review the triage result, confirm the support category, and route any unresolved issue.",
|
|
5411
|
+
completedDescription: "The call completed support triage and may need queue routing or human follow-up.",
|
|
5412
|
+
completedKind: "support-triage",
|
|
5413
|
+
completedTitle: "Review support triage",
|
|
5414
|
+
defaultCompletedCreatesTask: true,
|
|
5415
|
+
defaultDueInMs: 20 * 60000,
|
|
5416
|
+
defaultPriority: "normal",
|
|
5417
|
+
defaultQueue: "support-triage",
|
|
5418
|
+
description: "Creates support triage work for completed calls and urgent escalation/callback work for unresolved callers.",
|
|
5419
|
+
escalationQueue: "support-escalations"
|
|
5420
|
+
},
|
|
5421
|
+
"voicemail-callback": {
|
|
5422
|
+
completedAction: "No callback is required for completed calls.",
|
|
5423
|
+
completedDescription: "The call completed without requiring voicemail follow-up.",
|
|
5424
|
+
completedKind: "callback",
|
|
5425
|
+
completedTitle: "Completed call",
|
|
5426
|
+
defaultCompletedCreatesTask: false,
|
|
5427
|
+
defaultDueInMs: 15 * 60000,
|
|
5428
|
+
defaultPriority: "high",
|
|
5429
|
+
defaultQueue: "callbacks",
|
|
5430
|
+
description: "Creates callback work for voicemail, no-answer, failed, or escalated calls while ignoring completed calls.",
|
|
5431
|
+
escalationQueue: "callback-escalations"
|
|
5432
|
+
},
|
|
5433
|
+
"warm-transfer": {
|
|
5434
|
+
completedAction: "Confirm the handoff target received the caller context and close the transfer loop.",
|
|
5435
|
+
completedDescription: "The call is part of a warm-transfer flow and should be verified downstream.",
|
|
5436
|
+
completedKind: "transfer-check",
|
|
5437
|
+
completedTitle: "Verify warm transfer",
|
|
5438
|
+
defaultCompletedCreatesTask: false,
|
|
5439
|
+
defaultDueInMs: 10 * 60000,
|
|
5440
|
+
defaultPriority: "normal",
|
|
5441
|
+
defaultQueue: "transfer-verification",
|
|
5442
|
+
description: "Creates transfer verification work for transferred calls and escalation work when the handoff fails.",
|
|
5443
|
+
escalationQueue: "transfer-escalations"
|
|
5444
|
+
}
|
|
5445
|
+
};
|
|
5446
|
+
var buildRecipeTask = (input) => {
|
|
5447
|
+
const createdAt = input.review.generatedAt ?? Date.now();
|
|
5448
|
+
const queue = input.options.queue ?? input.defaults.defaultQueue;
|
|
5449
|
+
const target = input.options.target ?? input.review.postCall?.target;
|
|
5450
|
+
const common = {
|
|
5451
|
+
assignee: input.options.assignee,
|
|
5452
|
+
createdAt,
|
|
5453
|
+
history: [
|
|
5454
|
+
{
|
|
5455
|
+
actor: "system",
|
|
5456
|
+
at: createdAt,
|
|
5457
|
+
detail: input.review.postCall?.summary,
|
|
5458
|
+
type: "created"
|
|
5459
|
+
}
|
|
5460
|
+
],
|
|
5461
|
+
id: `${input.review.id}:${input.defaults.completedKind}`,
|
|
5462
|
+
intakeId: input.review.id,
|
|
5463
|
+
outcome: input.review.summary.outcome,
|
|
5464
|
+
priority: input.options.priority ?? input.defaults.defaultPriority,
|
|
5465
|
+
queue,
|
|
5466
|
+
reviewId: input.review.id,
|
|
5467
|
+
status: "open",
|
|
5468
|
+
target,
|
|
5469
|
+
updatedAt: createdAt
|
|
5470
|
+
};
|
|
5471
|
+
switch (input.disposition) {
|
|
5472
|
+
case "completed":
|
|
5473
|
+
if (!(input.options.completedCreatesTask ?? input.defaults.defaultCompletedCreatesTask)) {
|
|
5474
|
+
return null;
|
|
5475
|
+
}
|
|
5476
|
+
return {
|
|
5477
|
+
...common,
|
|
5478
|
+
description: input.defaults.completedDescription,
|
|
5479
|
+
kind: input.defaults.completedKind,
|
|
5480
|
+
recommendedAction: input.defaults.completedAction,
|
|
5481
|
+
title: target ? `${input.defaults.completedTitle}: ${target}` : input.defaults.completedTitle
|
|
5482
|
+
};
|
|
5483
|
+
case "voicemail":
|
|
5484
|
+
return {
|
|
5485
|
+
...common,
|
|
5486
|
+
description: input.review.postCall?.summary ?? "The caller reached voicemail and needs a callback.",
|
|
5487
|
+
id: `${input.review.id}:callback`,
|
|
5488
|
+
kind: "callback",
|
|
5489
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Call the customer back and continue the original flow.",
|
|
5490
|
+
title: target ? `Call back ${target}` : "Call back voicemail lead"
|
|
5491
|
+
};
|
|
5492
|
+
case "no-answer":
|
|
5493
|
+
return {
|
|
5494
|
+
...common,
|
|
5495
|
+
description: input.review.postCall?.summary ?? "The call did not reach a live respondent and should be retried.",
|
|
5496
|
+
id: `${input.review.id}:retry`,
|
|
5497
|
+
kind: "callback",
|
|
5498
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Retry the call or schedule a callback.",
|
|
5499
|
+
title: "Retry no-answer call"
|
|
5500
|
+
};
|
|
5501
|
+
case "transferred":
|
|
5502
|
+
return {
|
|
5503
|
+
...common,
|
|
5504
|
+
description: input.review.postCall?.summary ?? "The call was transferred and should be verified downstream.",
|
|
5505
|
+
id: `${input.review.id}:transfer-check`,
|
|
5506
|
+
kind: "transfer-check",
|
|
5507
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Confirm the receiving team got the caller context.",
|
|
5508
|
+
title: target ? `Verify transfer to ${target}` : "Verify call transfer"
|
|
5509
|
+
};
|
|
5510
|
+
case "escalated":
|
|
5511
|
+
return {
|
|
5512
|
+
...common,
|
|
5513
|
+
description: input.review.postCall?.summary ?? "The call escalated and needs human review.",
|
|
5514
|
+
id: `${input.review.id}:escalation`,
|
|
5515
|
+
kind: "escalation",
|
|
5516
|
+
priority: "urgent",
|
|
5517
|
+
queue: input.options.escalationQueue ?? input.defaults.escalationQueue,
|
|
5518
|
+
assignee: input.options.escalationAssignee ?? input.options.assignee,
|
|
5519
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Review the escalated call and respond immediately.",
|
|
5520
|
+
title: "Review escalated call"
|
|
5521
|
+
};
|
|
5522
|
+
case "failed":
|
|
5523
|
+
case "closed":
|
|
5524
|
+
return {
|
|
5525
|
+
...common,
|
|
5526
|
+
description: input.review.postCall?.summary ?? "The call ended before successful completion and needs review.",
|
|
5527
|
+
id: `${input.review.id}:retry-review`,
|
|
5528
|
+
kind: "retry-review",
|
|
5529
|
+
priority: "high",
|
|
5530
|
+
recommendedAction: input.review.postCall?.recommendedAction ?? "Inspect the call and decide whether to retry, escalate, or close.",
|
|
5531
|
+
title: "Inspect incomplete call"
|
|
5532
|
+
};
|
|
5533
|
+
default:
|
|
5534
|
+
return null;
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
var resolveVoiceOutcomeRecipe = (name, options = {}) => {
|
|
5538
|
+
const defaults = RECIPE_DEFAULTS[name];
|
|
5539
|
+
const taskPolicies = {
|
|
5540
|
+
completed: {
|
|
5541
|
+
assignee: options.assignee,
|
|
5542
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5543
|
+
name: `${name}-completed`,
|
|
5544
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5545
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5546
|
+
},
|
|
5547
|
+
escalated: {
|
|
5548
|
+
assignee: options.escalationAssignee ?? options.assignee,
|
|
5549
|
+
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 10 * 60000),
|
|
5550
|
+
name: `${name}-escalation`,
|
|
5551
|
+
priority: "urgent",
|
|
5552
|
+
queue: options.escalationQueue ?? defaults.escalationQueue
|
|
5553
|
+
},
|
|
5554
|
+
failed: {
|
|
5555
|
+
assignee: options.assignee,
|
|
5556
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5557
|
+
name: `${name}-failed-review`,
|
|
5558
|
+
priority: "high",
|
|
5559
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5560
|
+
},
|
|
5561
|
+
"no-answer": {
|
|
5562
|
+
assignee: options.assignee,
|
|
5563
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5564
|
+
name: `${name}-no-answer`,
|
|
5565
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5566
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5567
|
+
},
|
|
5568
|
+
transferred: {
|
|
5569
|
+
assignee: options.assignee,
|
|
5570
|
+
dueInMs: Math.min(options.dueInMs ?? defaults.defaultDueInMs, 20 * 60000),
|
|
5571
|
+
name: `${name}-transfer-check`,
|
|
5572
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5573
|
+
queue: name === "warm-transfer" ? options.queue ?? defaults.defaultQueue : "transfer-verification"
|
|
5574
|
+
},
|
|
5575
|
+
voicemail: {
|
|
5576
|
+
assignee: options.assignee,
|
|
5577
|
+
dueInMs: options.dueInMs ?? defaults.defaultDueInMs,
|
|
5578
|
+
name: `${name}-voicemail`,
|
|
5579
|
+
priority: options.priority ?? defaults.defaultPriority,
|
|
5580
|
+
queue: options.queue ?? defaults.defaultQueue
|
|
5581
|
+
}
|
|
5582
|
+
};
|
|
5583
|
+
const taskAssignmentRules = [
|
|
5584
|
+
{
|
|
5585
|
+
assign: options.escalationAssignee ?? options.assignee,
|
|
5586
|
+
description: `Route urgent ${name} work to the escalation lane.`,
|
|
5587
|
+
name: `${name}-urgent-routing`,
|
|
5588
|
+
queue: options.escalationQueue ?? defaults.escalationQueue,
|
|
5589
|
+
when: {
|
|
5590
|
+
priority: "urgent"
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
].filter((rule) => rule.assign || rule.queue);
|
|
5594
|
+
return {
|
|
5595
|
+
createTaskFromReview: ({ disposition, review }) => buildRecipeTask({
|
|
5596
|
+
defaults,
|
|
5597
|
+
disposition,
|
|
5598
|
+
options,
|
|
5599
|
+
review
|
|
5600
|
+
}),
|
|
5601
|
+
description: defaults.description,
|
|
5602
|
+
name,
|
|
5603
|
+
taskAssignmentRules,
|
|
5604
|
+
taskPolicies
|
|
5605
|
+
};
|
|
5606
|
+
};
|
|
5607
|
+
|
|
5608
|
+
// src/assistantMemory.ts
|
|
5609
|
+
var createMemoryId = (input) => `${input.assistantId}:${input.namespace}:${input.key}`;
|
|
5610
|
+
var createVoiceAssistantMemoryRecord = (input) => {
|
|
5611
|
+
const now = Date.now();
|
|
5612
|
+
return {
|
|
5613
|
+
...input,
|
|
5614
|
+
createdAt: input.createdAt ?? input.updatedAt ?? now,
|
|
5615
|
+
updatedAt: input.updatedAt ?? now
|
|
5616
|
+
};
|
|
5617
|
+
};
|
|
5618
|
+
var createVoiceMemoryAssistantMemoryStore = () => {
|
|
5619
|
+
const records = new Map;
|
|
5620
|
+
return {
|
|
5621
|
+
delete: async (input) => {
|
|
5622
|
+
records.delete(createMemoryId(input));
|
|
5623
|
+
},
|
|
5624
|
+
get: async (input) => records.get(createMemoryId(input)),
|
|
5625
|
+
list: async (input) => [...records.values()].filter((record) => record.assistantId === input.assistantId && (input.namespace === undefined || record.namespace === input.namespace)).sort((left, right) => right.updatedAt - left.updatedAt),
|
|
5626
|
+
set: async (input) => {
|
|
5627
|
+
const id = createMemoryId(input);
|
|
5628
|
+
const existing = records.get(id);
|
|
5629
|
+
const record = createVoiceAssistantMemoryRecord({
|
|
5630
|
+
...input,
|
|
5631
|
+
createdAt: input.createdAt ?? existing?.createdAt,
|
|
5632
|
+
updatedAt: input.updatedAt
|
|
5633
|
+
});
|
|
5634
|
+
records.set(id, record);
|
|
5635
|
+
return record;
|
|
5636
|
+
}
|
|
5637
|
+
};
|
|
5638
|
+
};
|
|
5639
|
+
var resolveVoiceAssistantMemoryNamespace = async (input) => typeof input.memory.namespace === "function" ? await input.memory.namespace(input) : input.memory.namespace;
|
|
5640
|
+
var createVoiceAssistantMemoryHandle = async (input) => {
|
|
5641
|
+
const namespace = await resolveVoiceAssistantMemoryNamespace({
|
|
5642
|
+
assistantId: input.assistantId,
|
|
5643
|
+
context: input.context,
|
|
5644
|
+
memory: input.memory,
|
|
5645
|
+
session: input.session
|
|
5646
|
+
});
|
|
5647
|
+
const trace = async (event) => {
|
|
5648
|
+
await input.trace?.append({
|
|
5649
|
+
at: Date.now(),
|
|
5650
|
+
payload: {
|
|
5651
|
+
assistantId: input.assistantId,
|
|
5652
|
+
namespace,
|
|
5653
|
+
...event
|
|
5654
|
+
},
|
|
5655
|
+
scenarioId: input.session.scenarioId,
|
|
5656
|
+
sessionId: input.session.id,
|
|
5657
|
+
type: "assistant.memory"
|
|
5658
|
+
});
|
|
5659
|
+
};
|
|
5660
|
+
return {
|
|
5661
|
+
delete: async (key) => {
|
|
5662
|
+
await input.memory.store.delete({
|
|
5663
|
+
assistantId: input.assistantId,
|
|
5664
|
+
key,
|
|
5665
|
+
namespace
|
|
5666
|
+
});
|
|
5667
|
+
await trace({
|
|
5668
|
+
action: "delete",
|
|
5669
|
+
key
|
|
5670
|
+
});
|
|
5671
|
+
},
|
|
5672
|
+
get: async (key) => {
|
|
5673
|
+
const record = await input.memory.store.get({
|
|
5674
|
+
assistantId: input.assistantId,
|
|
5675
|
+
key,
|
|
5676
|
+
namespace
|
|
5677
|
+
});
|
|
5678
|
+
await trace({
|
|
5679
|
+
action: "get",
|
|
5680
|
+
found: Boolean(record),
|
|
5681
|
+
key
|
|
5682
|
+
});
|
|
5683
|
+
return record?.value;
|
|
5684
|
+
},
|
|
5685
|
+
list: async () => {
|
|
5686
|
+
const records = await input.memory.store.list({
|
|
5687
|
+
assistantId: input.assistantId,
|
|
5688
|
+
namespace
|
|
5689
|
+
});
|
|
5690
|
+
await trace({
|
|
5691
|
+
action: "list",
|
|
5692
|
+
count: records.length
|
|
5693
|
+
});
|
|
5694
|
+
return records;
|
|
5695
|
+
},
|
|
5696
|
+
namespace,
|
|
5697
|
+
set: async (key, value, metadata) => {
|
|
5698
|
+
const record = await input.memory.store.set({
|
|
5699
|
+
assistantId: input.assistantId,
|
|
5700
|
+
key,
|
|
5701
|
+
metadata,
|
|
5702
|
+
namespace,
|
|
5703
|
+
value
|
|
5704
|
+
});
|
|
5705
|
+
await trace({
|
|
5706
|
+
action: "set",
|
|
5707
|
+
key
|
|
5708
|
+
});
|
|
5709
|
+
return record;
|
|
5710
|
+
}
|
|
5711
|
+
};
|
|
5712
|
+
};
|
|
5713
|
+
|
|
5714
|
+
// src/assistant.ts
|
|
5715
|
+
var hashString = (value) => {
|
|
5716
|
+
let hash = 2166136261;
|
|
5717
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
5718
|
+
hash ^= value.charCodeAt(index);
|
|
5719
|
+
hash = Math.imul(hash, 16777619);
|
|
5720
|
+
}
|
|
5721
|
+
return hash >>> 0;
|
|
5722
|
+
};
|
|
5723
|
+
var increment = (record, key) => {
|
|
5724
|
+
record[key] = (record[key] ?? 0) + 1;
|
|
5725
|
+
};
|
|
5726
|
+
var resolveOutcome = (result) => {
|
|
5727
|
+
if (result.transfer) {
|
|
5728
|
+
return "transferred";
|
|
5729
|
+
}
|
|
5730
|
+
if (result.escalate) {
|
|
5731
|
+
return "escalated";
|
|
5732
|
+
}
|
|
5733
|
+
if (result.voicemail) {
|
|
5734
|
+
return "voicemail";
|
|
5735
|
+
}
|
|
5736
|
+
if (result.noAnswer) {
|
|
5737
|
+
return "no-answer";
|
|
5738
|
+
}
|
|
5739
|
+
if (result.complete) {
|
|
5740
|
+
return "completed";
|
|
5741
|
+
}
|
|
5742
|
+
return "continued";
|
|
5743
|
+
};
|
|
5744
|
+
var resolveArtifactPlanName = (artifactPlan) => {
|
|
5745
|
+
const preset = artifactPlan?.preset;
|
|
5746
|
+
if (!preset) {
|
|
5747
|
+
return artifactPlan?.ops ? "custom" : undefined;
|
|
5748
|
+
}
|
|
5749
|
+
return typeof preset === "string" ? preset : preset.name;
|
|
5750
|
+
};
|
|
5751
|
+
var appendAssistantTrace = async (input) => {
|
|
5752
|
+
await input.trace?.append({
|
|
5753
|
+
at: Date.now(),
|
|
5754
|
+
payload: {
|
|
5755
|
+
assistantId: input.assistantId,
|
|
5756
|
+
...input.event
|
|
5757
|
+
},
|
|
5758
|
+
scenarioId: input.session.scenarioId,
|
|
5759
|
+
sessionId: input.session.id,
|
|
5760
|
+
turnId: input.turnId,
|
|
5761
|
+
type: input.type
|
|
5762
|
+
});
|
|
5763
|
+
};
|
|
5764
|
+
var resolvePresetOps = (artifactPlan) => {
|
|
5765
|
+
const preset = artifactPlan?.preset;
|
|
5766
|
+
if (!preset) {
|
|
5767
|
+
return artifactPlan?.ops;
|
|
5768
|
+
}
|
|
5769
|
+
const recipe = typeof preset === "string" ? resolveVoiceOutcomeRecipe(preset) : resolveVoiceOutcomeRecipe(preset.name, preset.options);
|
|
5770
|
+
return {
|
|
5771
|
+
...recipe,
|
|
5772
|
+
...artifactPlan?.ops
|
|
5773
|
+
};
|
|
5774
|
+
};
|
|
5775
|
+
var mergeOps = (base, override) => {
|
|
5776
|
+
if (!base && !override) {
|
|
5777
|
+
return;
|
|
5778
|
+
}
|
|
5779
|
+
return {
|
|
5780
|
+
...base,
|
|
5781
|
+
...override,
|
|
5782
|
+
taskAssignmentRules: base?.taskAssignmentRules || override?.taskAssignmentRules ? [
|
|
5783
|
+
...base?.taskAssignmentRules ?? [],
|
|
5784
|
+
...override?.taskAssignmentRules ?? []
|
|
5785
|
+
] : undefined,
|
|
5786
|
+
taskPolicies: base?.taskPolicies || override?.taskPolicies ? {
|
|
5787
|
+
...base?.taskPolicies ?? {},
|
|
5788
|
+
...override?.taskPolicies ?? {}
|
|
5789
|
+
} : undefined
|
|
5790
|
+
};
|
|
5791
|
+
};
|
|
5792
|
+
var createVoiceExperiment = (options) => {
|
|
5793
|
+
if (!options.variants.length) {
|
|
5794
|
+
throw new Error("createVoiceExperiment requires at least one variant.");
|
|
5795
|
+
}
|
|
5796
|
+
const firstVariant = options.variants[0];
|
|
5797
|
+
return {
|
|
5798
|
+
id: options.id,
|
|
5799
|
+
resolve: (input) => {
|
|
5800
|
+
const selected = options.selectVariant?.({
|
|
5801
|
+
...input,
|
|
5802
|
+
variants: options.variants
|
|
5803
|
+
});
|
|
5804
|
+
if (selected && typeof selected !== "object") {
|
|
5805
|
+
const variant = options.variants.find((item) => item.id === selected);
|
|
5806
|
+
if (variant) {
|
|
5807
|
+
return variant;
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
if (selected && typeof selected === "object" && "id" in selected) {
|
|
5811
|
+
return selected;
|
|
5812
|
+
}
|
|
5813
|
+
const totalWeight = options.variants.reduce((total, variant) => total + Math.max(0, variant.weight ?? 1), 0);
|
|
5814
|
+
if (totalWeight <= 0) {
|
|
5815
|
+
return firstVariant;
|
|
5816
|
+
}
|
|
5817
|
+
const bucket = hashString(`${options.id}:${input.assistantId}:${input.session.id}`) % totalWeight;
|
|
5818
|
+
let cursor = 0;
|
|
5819
|
+
for (const variant of options.variants) {
|
|
5820
|
+
cursor += Math.max(0, variant.weight ?? 1);
|
|
5821
|
+
if (bucket < cursor) {
|
|
5822
|
+
return variant;
|
|
5823
|
+
}
|
|
5824
|
+
}
|
|
5825
|
+
return firstVariant;
|
|
5826
|
+
},
|
|
5827
|
+
variants: options.variants
|
|
5828
|
+
};
|
|
5829
|
+
};
|
|
5830
|
+
var createVoiceAssistant = (options) => {
|
|
5831
|
+
const ops = mergeOps(resolvePresetOps(options.artifactPlan), options.ops);
|
|
5832
|
+
const artifactPlanName = resolveArtifactPlanName(options.artifactPlan);
|
|
5833
|
+
let agent;
|
|
5834
|
+
const baseModelOptions = "model" in options && options.model ? {
|
|
5835
|
+
maxToolRounds: options.maxToolRounds,
|
|
5836
|
+
model: options.model,
|
|
5837
|
+
system: options.system,
|
|
5838
|
+
tools: options.tools
|
|
5839
|
+
} : undefined;
|
|
5840
|
+
if ("agent" in options && options.agent) {
|
|
5841
|
+
agent = options.agent;
|
|
5842
|
+
} else if ("agents" in options && options.agents) {
|
|
5843
|
+
agent = createVoiceAgentSquad({
|
|
5844
|
+
agents: options.agents,
|
|
5845
|
+
defaultAgentId: options.defaultAgentId,
|
|
5846
|
+
id: options.id,
|
|
5847
|
+
maxHandoffsPerTurn: options.maxHandoffsPerTurn,
|
|
5848
|
+
selectAgent: options.selectAgent,
|
|
5849
|
+
trace: options.trace
|
|
5850
|
+
});
|
|
5851
|
+
} else {
|
|
5852
|
+
agent = createVoiceAgent({
|
|
5853
|
+
id: options.id,
|
|
5854
|
+
maxToolRounds: options.maxToolRounds,
|
|
5855
|
+
model: options.model,
|
|
5856
|
+
system: options.system,
|
|
5857
|
+
trace: options.trace,
|
|
5858
|
+
tools: options.tools
|
|
5859
|
+
});
|
|
5860
|
+
}
|
|
5861
|
+
const onTurn = async (input) => {
|
|
5862
|
+
const memory = options.memory ? await createVoiceAssistantMemoryHandle({
|
|
5863
|
+
assistantId: options.id,
|
|
5864
|
+
context: input.context,
|
|
5865
|
+
memory: options.memory,
|
|
5866
|
+
session: input.session,
|
|
5867
|
+
trace: options.trace
|
|
5868
|
+
}) : undefined;
|
|
5869
|
+
const guardrailInput = {
|
|
5870
|
+
...input,
|
|
5871
|
+
assistantId: options.id,
|
|
5872
|
+
memory
|
|
5873
|
+
};
|
|
5874
|
+
if (memory) {
|
|
5875
|
+
await options.memoryLifecycle?.beforeTurn?.({
|
|
5876
|
+
...input,
|
|
5877
|
+
assistantId: options.id,
|
|
5878
|
+
memory
|
|
5879
|
+
});
|
|
5880
|
+
}
|
|
5881
|
+
const blocked = await options.guardrails?.beforeTurn?.(guardrailInput);
|
|
5882
|
+
if (blocked) {
|
|
5883
|
+
if (memory) {
|
|
5884
|
+
await options.memoryLifecycle?.afterTurn?.({
|
|
5885
|
+
...input,
|
|
5886
|
+
assistantId: options.id,
|
|
5887
|
+
memory,
|
|
5888
|
+
result: blocked
|
|
5889
|
+
});
|
|
5890
|
+
}
|
|
5891
|
+
await appendAssistantTrace({
|
|
5892
|
+
assistantId: options.id,
|
|
5893
|
+
event: {
|
|
5894
|
+
action: "blocked",
|
|
5895
|
+
artifactPlan: artifactPlanName,
|
|
5896
|
+
outcome: resolveOutcome(blocked)
|
|
5897
|
+
},
|
|
5898
|
+
session: input.session,
|
|
5899
|
+
trace: options.trace,
|
|
5900
|
+
turnId: input.turn.id,
|
|
5901
|
+
type: "assistant.guardrail"
|
|
5902
|
+
});
|
|
5903
|
+
await appendAssistantTrace({
|
|
5904
|
+
assistantId: options.id,
|
|
5905
|
+
event: {
|
|
5906
|
+
artifactPlan: artifactPlanName,
|
|
5907
|
+
blocked: true,
|
|
5908
|
+
experimentId: options.experiment?.id,
|
|
5909
|
+
outcome: resolveOutcome(blocked)
|
|
5910
|
+
},
|
|
5911
|
+
session: input.session,
|
|
5912
|
+
trace: options.trace,
|
|
5913
|
+
turnId: input.turn.id,
|
|
5914
|
+
type: "assistant.run"
|
|
5915
|
+
});
|
|
5916
|
+
return blocked;
|
|
5917
|
+
}
|
|
5918
|
+
const startedAt = Date.now();
|
|
5919
|
+
const variant = options.experiment?.resolve({
|
|
5920
|
+
assistantId: options.id,
|
|
5921
|
+
context: input.context,
|
|
5922
|
+
session: input.session,
|
|
5923
|
+
turnId: input.turn.id
|
|
5924
|
+
});
|
|
5925
|
+
const runner = variant && baseModelOptions ? createVoiceAgent({
|
|
5926
|
+
id: `${options.id}:${variant.id}`,
|
|
5927
|
+
maxToolRounds: variant.maxToolRounds ?? baseModelOptions.maxToolRounds,
|
|
5928
|
+
model: variant.model ?? baseModelOptions.model,
|
|
5929
|
+
system: variant.system ?? baseModelOptions.system,
|
|
5930
|
+
trace: options.trace,
|
|
5931
|
+
tools: variant.tools ?? baseModelOptions.tools
|
|
5932
|
+
}) : agent;
|
|
5933
|
+
const runResult = await runner.run(input) ?? {};
|
|
5934
|
+
const result = runResult;
|
|
5935
|
+
const guarded = await options.guardrails?.afterTurn?.({
|
|
5936
|
+
...guardrailInput,
|
|
5937
|
+
result
|
|
5938
|
+
});
|
|
5939
|
+
const finalResult = guarded ?? result;
|
|
5940
|
+
if (memory) {
|
|
5941
|
+
await options.memoryLifecycle?.afterTurn?.({
|
|
5942
|
+
...input,
|
|
5943
|
+
assistantId: options.id,
|
|
5944
|
+
memory,
|
|
5945
|
+
result: finalResult
|
|
5356
5946
|
});
|
|
5357
|
-
toolResults.push(...result.toolResults);
|
|
5358
5947
|
}
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5948
|
+
if (guarded) {
|
|
5949
|
+
await appendAssistantTrace({
|
|
5950
|
+
assistantId: options.id,
|
|
5951
|
+
event: {
|
|
5952
|
+
action: "rewritten",
|
|
5953
|
+
artifactPlan: artifactPlanName,
|
|
5954
|
+
experimentId: options.experiment?.id,
|
|
5955
|
+
outcome: resolveOutcome(finalResult),
|
|
5956
|
+
variantId: variant?.id
|
|
5957
|
+
},
|
|
5958
|
+
session: input.session,
|
|
5959
|
+
trace: options.trace,
|
|
5960
|
+
turnId: input.turn.id,
|
|
5961
|
+
type: "assistant.guardrail"
|
|
5962
|
+
});
|
|
5963
|
+
}
|
|
5964
|
+
await appendAssistantTrace({
|
|
5965
|
+
assistantId: options.id,
|
|
5966
|
+
event: {
|
|
5967
|
+
artifactPlan: artifactPlanName,
|
|
5968
|
+
blocked: false,
|
|
5969
|
+
elapsedMs: Date.now() - startedAt,
|
|
5970
|
+
escalated: Boolean(finalResult.escalate),
|
|
5971
|
+
experimentId: options.experiment?.id,
|
|
5972
|
+
outcome: resolveOutcome(finalResult),
|
|
5973
|
+
toolNames: result.toolResults?.map((tool) => tool.toolName) ?? [],
|
|
5974
|
+
transferred: Boolean(finalResult.transfer),
|
|
5975
|
+
variantId: variant?.id
|
|
5976
|
+
},
|
|
5977
|
+
session: input.session,
|
|
5978
|
+
trace: options.trace,
|
|
5979
|
+
turnId: input.turn.id,
|
|
5980
|
+
type: "assistant.run"
|
|
5981
|
+
});
|
|
5982
|
+
return finalResult;
|
|
5364
5983
|
};
|
|
5365
5984
|
return {
|
|
5985
|
+
agent,
|
|
5366
5986
|
id: options.id,
|
|
5367
|
-
onTurn
|
|
5368
|
-
|
|
5987
|
+
onTurn,
|
|
5988
|
+
ops,
|
|
5989
|
+
route: (overrides) => ({
|
|
5990
|
+
...overrides,
|
|
5991
|
+
onComplete: overrides.onComplete ?? (() => {
|
|
5992
|
+
return;
|
|
5993
|
+
}),
|
|
5994
|
+
onTurn
|
|
5995
|
+
})
|
|
5996
|
+
};
|
|
5997
|
+
};
|
|
5998
|
+
var summarizeVoiceAssistantRuns = async (input) => {
|
|
5999
|
+
const events = Array.isArray(input) ? input : input.events ?? await input.store?.list() ?? [];
|
|
6000
|
+
const assistantRuns = events.filter((event) => event.type === "assistant.run");
|
|
6001
|
+
const guardrails = events.filter((event) => event.type === "assistant.guardrail");
|
|
6002
|
+
const byAssistant = new Map;
|
|
6003
|
+
const getSummary = (assistantId) => {
|
|
6004
|
+
let summary = byAssistant.get(assistantId);
|
|
6005
|
+
if (!summary) {
|
|
6006
|
+
summary = {
|
|
6007
|
+
assistantId,
|
|
6008
|
+
artifactPlans: {},
|
|
6009
|
+
blockedGuardrailCount: 0,
|
|
6010
|
+
elapsedCount: 0,
|
|
6011
|
+
elapsedTotal: 0,
|
|
6012
|
+
escalationCount: 0,
|
|
6013
|
+
experiments: {},
|
|
6014
|
+
guardrailCount: 0,
|
|
6015
|
+
memory: {
|
|
6016
|
+
deletes: 0,
|
|
6017
|
+
gets: 0,
|
|
6018
|
+
lists: 0,
|
|
6019
|
+
sets: 0
|
|
6020
|
+
},
|
|
6021
|
+
outcomes: {},
|
|
6022
|
+
runCount: 0,
|
|
6023
|
+
sessionIds: new Set,
|
|
6024
|
+
sessions: 0,
|
|
6025
|
+
toolCalls: {},
|
|
6026
|
+
transferCount: 0,
|
|
6027
|
+
variants: {}
|
|
6028
|
+
};
|
|
6029
|
+
byAssistant.set(assistantId, summary);
|
|
6030
|
+
}
|
|
6031
|
+
return summary;
|
|
6032
|
+
};
|
|
6033
|
+
for (const event of assistantRuns) {
|
|
6034
|
+
const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
|
|
6035
|
+
const summary = getSummary(assistantId);
|
|
6036
|
+
summary.runCount += 1;
|
|
6037
|
+
summary.sessionIds.add(event.sessionId);
|
|
6038
|
+
if (typeof event.payload.artifactPlan === "string") {
|
|
6039
|
+
increment(summary.artifactPlans, event.payload.artifactPlan);
|
|
6040
|
+
}
|
|
6041
|
+
if (typeof event.payload.experimentId === "string") {
|
|
6042
|
+
increment(summary.experiments, event.payload.experimentId);
|
|
6043
|
+
}
|
|
6044
|
+
if (typeof event.payload.variantId === "string") {
|
|
6045
|
+
increment(summary.variants, event.payload.variantId);
|
|
6046
|
+
}
|
|
6047
|
+
if (typeof event.payload.outcome === "string") {
|
|
6048
|
+
increment(summary.outcomes, event.payload.outcome);
|
|
6049
|
+
}
|
|
6050
|
+
if (event.payload.escalated === true) {
|
|
6051
|
+
summary.escalationCount += 1;
|
|
6052
|
+
}
|
|
6053
|
+
if (event.payload.transferred === true) {
|
|
6054
|
+
summary.transferCount += 1;
|
|
6055
|
+
}
|
|
6056
|
+
if (event.payload.blocked === true) {
|
|
6057
|
+
summary.blockedGuardrailCount += 1;
|
|
6058
|
+
}
|
|
6059
|
+
if (typeof event.payload.elapsedMs === "number") {
|
|
6060
|
+
summary.elapsedCount += 1;
|
|
6061
|
+
summary.elapsedTotal += event.payload.elapsedMs;
|
|
6062
|
+
}
|
|
6063
|
+
if (Array.isArray(event.payload.toolNames)) {
|
|
6064
|
+
for (const toolName of event.payload.toolNames) {
|
|
6065
|
+
if (typeof toolName === "string") {
|
|
6066
|
+
increment(summary.toolCalls, toolName);
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
}
|
|
6071
|
+
for (const event of guardrails) {
|
|
6072
|
+
const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
|
|
6073
|
+
const summary = getSummary(assistantId);
|
|
6074
|
+
summary.guardrailCount += 1;
|
|
6075
|
+
}
|
|
6076
|
+
for (const event of events.filter((event2) => event2.type === "assistant.memory")) {
|
|
6077
|
+
const assistantId = typeof event.payload.assistantId === "string" ? event.payload.assistantId : "unknown";
|
|
6078
|
+
const summary = getSummary(assistantId);
|
|
6079
|
+
switch (event.payload.action) {
|
|
6080
|
+
case "delete":
|
|
6081
|
+
summary.memory.deletes += 1;
|
|
6082
|
+
break;
|
|
6083
|
+
case "get":
|
|
6084
|
+
summary.memory.gets += 1;
|
|
6085
|
+
break;
|
|
6086
|
+
case "list":
|
|
6087
|
+
summary.memory.lists += 1;
|
|
6088
|
+
break;
|
|
6089
|
+
case "set":
|
|
6090
|
+
summary.memory.sets += 1;
|
|
6091
|
+
break;
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
const assistants = [...byAssistant.values()].map(({ elapsedCount, elapsedTotal, sessionIds, ...summary }) => ({
|
|
6095
|
+
...summary,
|
|
6096
|
+
averageElapsedMs: elapsedCount > 0 ? Math.round(elapsedTotal / elapsedCount) : undefined,
|
|
6097
|
+
sessions: sessionIds.size
|
|
6098
|
+
}));
|
|
6099
|
+
return {
|
|
6100
|
+
assistants: assistants.sort((left, right) => left.assistantId.localeCompare(right.assistantId)),
|
|
6101
|
+
totalRuns: assistantRuns.length
|
|
5369
6102
|
};
|
|
5370
6103
|
};
|
|
5371
6104
|
// src/fileStore.ts
|
|
@@ -6052,6 +6785,7 @@ var listJsonFiles = async (directory) => {
|
|
|
6052
6785
|
};
|
|
6053
6786
|
var encodeStoreId = (id) => `${encodeURIComponent(id)}.json`;
|
|
6054
6787
|
var resolveFilePath = (directory, id) => join(directory, encodeStoreId(id));
|
|
6788
|
+
var createMemoryStoreId = (input) => `${input.assistantId}:${input.namespace}:${input.key}`;
|
|
6055
6789
|
var readJsonFile = async (path) => JSON.parse(await readFile(path, "utf8"));
|
|
6056
6790
|
var writeJsonFile = async (path, value, options) => {
|
|
6057
6791
|
await mkdir(options.directory, {
|
|
@@ -6271,6 +7005,40 @@ var createVoiceFileTraceSinkDeliveryStore = (options) => {
|
|
|
6271
7005
|
};
|
|
6272
7006
|
return { get, list, remove, set };
|
|
6273
7007
|
};
|
|
7008
|
+
var createVoiceFileAssistantMemoryStore = (options) => {
|
|
7009
|
+
const get = async (input) => {
|
|
7010
|
+
const path = resolveFilePath(options.directory, createMemoryStoreId(input));
|
|
7011
|
+
try {
|
|
7012
|
+
return await readJsonFile(path);
|
|
7013
|
+
} catch (error) {
|
|
7014
|
+
if (error.code === "ENOENT") {
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
throw error;
|
|
7018
|
+
}
|
|
7019
|
+
};
|
|
7020
|
+
const list = async (input) => {
|
|
7021
|
+
const files = await listJsonFiles(options.directory);
|
|
7022
|
+
const records = await Promise.all(files.map((file) => readJsonFile(file)));
|
|
7023
|
+
return records.filter((record) => record.assistantId === input.assistantId && (input.namespace === undefined || record.namespace === input.namespace)).sort((left, right) => right.updatedAt - left.updatedAt);
|
|
7024
|
+
};
|
|
7025
|
+
const set = async (input) => {
|
|
7026
|
+
const existing = await get(input);
|
|
7027
|
+
const record = createVoiceAssistantMemoryRecord({
|
|
7028
|
+
...input,
|
|
7029
|
+
createdAt: input.createdAt ?? existing?.createdAt,
|
|
7030
|
+
updatedAt: input.updatedAt
|
|
7031
|
+
});
|
|
7032
|
+
await writeJsonFile(resolveFilePath(options.directory, createMemoryStoreId(record)), record, options);
|
|
7033
|
+
return record;
|
|
7034
|
+
};
|
|
7035
|
+
const remove = async (input) => {
|
|
7036
|
+
await rm(resolveFilePath(options.directory, createMemoryStoreId(input)), {
|
|
7037
|
+
force: true
|
|
7038
|
+
});
|
|
7039
|
+
};
|
|
7040
|
+
return { delete: remove, get, list, set };
|
|
7041
|
+
};
|
|
6274
7042
|
var createVoiceFileRuntimeStorage = (options) => ({
|
|
6275
7043
|
events: createVoiceFileIntegrationEventStore({
|
|
6276
7044
|
...options,
|
|
@@ -6280,6 +7048,10 @@ var createVoiceFileRuntimeStorage = (options) => ({
|
|
|
6280
7048
|
...options,
|
|
6281
7049
|
directory: join(options.directory, "external-objects")
|
|
6282
7050
|
}),
|
|
7051
|
+
memories: createVoiceFileAssistantMemoryStore({
|
|
7052
|
+
...options,
|
|
7053
|
+
directory: join(options.directory, "memories")
|
|
7054
|
+
}),
|
|
6283
7055
|
reviews: createVoiceFileReviewStore({
|
|
6284
7056
|
...options,
|
|
6285
7057
|
directory: join(options.directory, "reviews")
|
|
@@ -6312,6 +7084,593 @@ var createStoredVoiceExternalObjectMap = (mapping) => createVoiceExternalObjectM
|
|
|
6312
7084
|
sourceId: mapping.sourceId,
|
|
6313
7085
|
sourceType: mapping.sourceType
|
|
6314
7086
|
});
|
|
7087
|
+
// src/modelAdapters.ts
|
|
7088
|
+
var OUTPUT_SCHEMA = {
|
|
7089
|
+
additionalProperties: false,
|
|
7090
|
+
properties: {
|
|
7091
|
+
assistantText: {
|
|
7092
|
+
type: "string"
|
|
7093
|
+
},
|
|
7094
|
+
complete: {
|
|
7095
|
+
type: "boolean"
|
|
7096
|
+
},
|
|
7097
|
+
escalate: {
|
|
7098
|
+
additionalProperties: false,
|
|
7099
|
+
properties: {
|
|
7100
|
+
metadata: {
|
|
7101
|
+
additionalProperties: true,
|
|
7102
|
+
type: "object"
|
|
7103
|
+
},
|
|
7104
|
+
reason: {
|
|
7105
|
+
type: "string"
|
|
7106
|
+
}
|
|
7107
|
+
},
|
|
7108
|
+
required: ["reason"],
|
|
7109
|
+
type: "object"
|
|
7110
|
+
},
|
|
7111
|
+
noAnswer: {
|
|
7112
|
+
additionalProperties: false,
|
|
7113
|
+
properties: {
|
|
7114
|
+
metadata: {
|
|
7115
|
+
additionalProperties: true,
|
|
7116
|
+
type: "object"
|
|
7117
|
+
}
|
|
7118
|
+
},
|
|
7119
|
+
type: "object"
|
|
7120
|
+
},
|
|
7121
|
+
result: {
|
|
7122
|
+
additionalProperties: true,
|
|
7123
|
+
type: "object"
|
|
7124
|
+
},
|
|
7125
|
+
transfer: {
|
|
7126
|
+
additionalProperties: false,
|
|
7127
|
+
properties: {
|
|
7128
|
+
metadata: {
|
|
7129
|
+
additionalProperties: true,
|
|
7130
|
+
type: "object"
|
|
7131
|
+
},
|
|
7132
|
+
reason: {
|
|
7133
|
+
type: "string"
|
|
7134
|
+
},
|
|
7135
|
+
target: {
|
|
7136
|
+
type: "string"
|
|
7137
|
+
}
|
|
7138
|
+
},
|
|
7139
|
+
required: ["target"],
|
|
7140
|
+
type: "object"
|
|
7141
|
+
},
|
|
7142
|
+
voicemail: {
|
|
7143
|
+
additionalProperties: false,
|
|
7144
|
+
properties: {
|
|
7145
|
+
metadata: {
|
|
7146
|
+
additionalProperties: true,
|
|
7147
|
+
type: "object"
|
|
7148
|
+
}
|
|
7149
|
+
},
|
|
7150
|
+
type: "object"
|
|
7151
|
+
}
|
|
7152
|
+
},
|
|
7153
|
+
type: "object"
|
|
7154
|
+
};
|
|
7155
|
+
var ROUTE_RESULT_INSTRUCTION = "Return only a JSON object with assistantText, complete, transfer, escalate, voicemail, noAnswer, and result when you are not calling tools. Only set transfer, escalate, voicemail, or noAnswer when the user explicitly asks for that lifecycle outcome or a tool result says that exact outcome. Do not infer voicemail from generic words like voice, voice app, or voice integration.";
|
|
7156
|
+
var stripJSONCodeFence = (value) => {
|
|
7157
|
+
const trimmed = value.trim();
|
|
7158
|
+
const match = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
|
|
7159
|
+
return match?.[1]?.trim() ?? value;
|
|
7160
|
+
};
|
|
7161
|
+
var parseJSON = (value) => {
|
|
7162
|
+
try {
|
|
7163
|
+
const parsed = JSON.parse(stripJSONCodeFence(value));
|
|
7164
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
7165
|
+
} catch {
|
|
7166
|
+
return {
|
|
7167
|
+
assistantText: value
|
|
7168
|
+
};
|
|
7169
|
+
}
|
|
7170
|
+
};
|
|
7171
|
+
var parseJSONValue = (value) => {
|
|
7172
|
+
try {
|
|
7173
|
+
return JSON.parse(value);
|
|
7174
|
+
} catch {
|
|
7175
|
+
return value;
|
|
7176
|
+
}
|
|
7177
|
+
};
|
|
7178
|
+
var getMessageToolCalls = (message) => {
|
|
7179
|
+
const toolCalls = message.metadata?.toolCalls;
|
|
7180
|
+
return Array.isArray(toolCalls) ? toolCalls.filter((toolCall) => toolCall && typeof toolCall === "object" && typeof toolCall.name === "string") : [];
|
|
7181
|
+
};
|
|
7182
|
+
var createHTTPError = (provider, response) => new Error(`${provider} voice assistant model failed: HTTP ${response.status}`);
|
|
7183
|
+
var sleep4 = (ms) => new Promise((resolve2) => {
|
|
7184
|
+
setTimeout(resolve2, ms);
|
|
7185
|
+
});
|
|
7186
|
+
var normalizeRouteOutput = (output) => {
|
|
7187
|
+
const result = {};
|
|
7188
|
+
if (typeof output.assistantText === "string") {
|
|
7189
|
+
result.assistantText = output.assistantText;
|
|
7190
|
+
}
|
|
7191
|
+
if (typeof output.complete === "boolean") {
|
|
7192
|
+
result.complete = output.complete;
|
|
7193
|
+
}
|
|
7194
|
+
if (output.result !== undefined) {
|
|
7195
|
+
result.result = output.result;
|
|
7196
|
+
}
|
|
7197
|
+
if (output.transfer && typeof output.transfer === "object") {
|
|
7198
|
+
const transfer = output.transfer;
|
|
7199
|
+
if (typeof transfer.target === "string") {
|
|
7200
|
+
result.transfer = {
|
|
7201
|
+
metadata: transfer.metadata && typeof transfer.metadata === "object" ? transfer.metadata : undefined,
|
|
7202
|
+
reason: typeof transfer.reason === "string" ? transfer.reason : undefined,
|
|
7203
|
+
target: transfer.target
|
|
7204
|
+
};
|
|
7205
|
+
}
|
|
7206
|
+
}
|
|
7207
|
+
if (output.escalate && typeof output.escalate === "object") {
|
|
7208
|
+
const escalate = output.escalate;
|
|
7209
|
+
if (typeof escalate.reason === "string") {
|
|
7210
|
+
result.escalate = {
|
|
7211
|
+
metadata: escalate.metadata && typeof escalate.metadata === "object" ? escalate.metadata : undefined,
|
|
7212
|
+
reason: escalate.reason
|
|
7213
|
+
};
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
if (output.voicemail && typeof output.voicemail === "object") {
|
|
7217
|
+
const voicemail = output.voicemail;
|
|
7218
|
+
result.voicemail = {
|
|
7219
|
+
metadata: voicemail.metadata && typeof voicemail.metadata === "object" ? voicemail.metadata : undefined
|
|
7220
|
+
};
|
|
7221
|
+
}
|
|
7222
|
+
if (output.noAnswer && typeof output.noAnswer === "object") {
|
|
7223
|
+
const noAnswer = output.noAnswer;
|
|
7224
|
+
result.noAnswer = {
|
|
7225
|
+
metadata: noAnswer.metadata && typeof noAnswer.metadata === "object" ? noAnswer.metadata : undefined
|
|
7226
|
+
};
|
|
7227
|
+
}
|
|
7228
|
+
return result;
|
|
7229
|
+
};
|
|
7230
|
+
var createJSONVoiceAssistantModel = (options) => ({
|
|
7231
|
+
generate: async (input) => {
|
|
7232
|
+
const output = await options.generate(input);
|
|
7233
|
+
if ("assistantText" in output || "toolCalls" in output || "complete" in output || "transfer" in output || "escalate" in output) {
|
|
7234
|
+
return output;
|
|
7235
|
+
}
|
|
7236
|
+
return options.mapOutput?.(output) ?? normalizeRouteOutput(output);
|
|
7237
|
+
}
|
|
7238
|
+
});
|
|
7239
|
+
var messageToOpenAIInput = (message) => {
|
|
7240
|
+
if (message.role === "tool") {
|
|
7241
|
+
return [
|
|
7242
|
+
{
|
|
7243
|
+
call_id: message.toolCallId ?? message.name ?? crypto.randomUUID(),
|
|
7244
|
+
output: message.content,
|
|
7245
|
+
type: "function_call_output"
|
|
7246
|
+
}
|
|
7247
|
+
];
|
|
7248
|
+
}
|
|
7249
|
+
const toolCalls = getMessageToolCalls(message);
|
|
7250
|
+
if (message.role === "assistant" && toolCalls.length) {
|
|
7251
|
+
return toolCalls.map((toolCall) => ({
|
|
7252
|
+
arguments: JSON.stringify(toolCall.args),
|
|
7253
|
+
call_id: toolCall.id ?? crypto.randomUUID(),
|
|
7254
|
+
name: toolCall.name,
|
|
7255
|
+
type: "function_call"
|
|
7256
|
+
}));
|
|
7257
|
+
}
|
|
7258
|
+
return [
|
|
7259
|
+
{
|
|
7260
|
+
content: message.content,
|
|
7261
|
+
role: message.role === "system" ? "developer" : message.role
|
|
7262
|
+
}
|
|
7263
|
+
];
|
|
7264
|
+
};
|
|
7265
|
+
var messagesToOpenAIInput = (messages) => messages.flatMap(messageToOpenAIInput);
|
|
7266
|
+
var messageToAnthropicMessage = (message) => {
|
|
7267
|
+
if (message.role === "system") {
|
|
7268
|
+
return;
|
|
7269
|
+
}
|
|
7270
|
+
if (message.role === "tool") {
|
|
7271
|
+
if (!message.toolCallId) {
|
|
7272
|
+
return {
|
|
7273
|
+
content: `Tool result from ${message.name ?? "tool"}: ${message.content}`,
|
|
7274
|
+
role: "user"
|
|
7275
|
+
};
|
|
7276
|
+
}
|
|
7277
|
+
return {
|
|
7278
|
+
content: [
|
|
7279
|
+
{
|
|
7280
|
+
content: message.content,
|
|
7281
|
+
tool_use_id: message.toolCallId,
|
|
7282
|
+
type: "tool_result"
|
|
7283
|
+
}
|
|
7284
|
+
],
|
|
7285
|
+
role: "user"
|
|
7286
|
+
};
|
|
7287
|
+
}
|
|
7288
|
+
const toolCalls = getMessageToolCalls(message);
|
|
7289
|
+
if (message.role === "assistant" && toolCalls.length) {
|
|
7290
|
+
return {
|
|
7291
|
+
content: [
|
|
7292
|
+
...message.content ? [
|
|
7293
|
+
{
|
|
7294
|
+
text: message.content,
|
|
7295
|
+
type: "text"
|
|
7296
|
+
}
|
|
7297
|
+
] : [],
|
|
7298
|
+
...toolCalls.map((toolCall) => ({
|
|
7299
|
+
id: toolCall.id ?? crypto.randomUUID(),
|
|
7300
|
+
input: toolCall.args,
|
|
7301
|
+
name: toolCall.name,
|
|
7302
|
+
type: "tool_use"
|
|
7303
|
+
}))
|
|
7304
|
+
],
|
|
7305
|
+
role: "assistant"
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7308
|
+
return {
|
|
7309
|
+
content: message.content,
|
|
7310
|
+
role: message.role
|
|
7311
|
+
};
|
|
7312
|
+
};
|
|
7313
|
+
var toGeminiSchema = (schema) => {
|
|
7314
|
+
const next = {};
|
|
7315
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
7316
|
+
if (key === "additionalProperties") {
|
|
7317
|
+
continue;
|
|
7318
|
+
}
|
|
7319
|
+
if (key === "type" && typeof value === "string") {
|
|
7320
|
+
next[key] = value.toUpperCase();
|
|
7321
|
+
continue;
|
|
7322
|
+
}
|
|
7323
|
+
if (Array.isArray(value)) {
|
|
7324
|
+
next[key] = value.map((item) => item && typeof item === "object" ? toGeminiSchema(item) : item);
|
|
7325
|
+
continue;
|
|
7326
|
+
}
|
|
7327
|
+
if (value && typeof value === "object") {
|
|
7328
|
+
next[key] = toGeminiSchema(value);
|
|
7329
|
+
continue;
|
|
7330
|
+
}
|
|
7331
|
+
next[key] = value;
|
|
7332
|
+
}
|
|
7333
|
+
return next;
|
|
7334
|
+
};
|
|
7335
|
+
var messageToGeminiContent = (message) => {
|
|
7336
|
+
if (message.role === "system") {
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
if (message.role === "tool") {
|
|
7340
|
+
return {
|
|
7341
|
+
parts: [
|
|
7342
|
+
{
|
|
7343
|
+
functionResponse: {
|
|
7344
|
+
id: message.toolCallId,
|
|
7345
|
+
name: message.name ?? "tool",
|
|
7346
|
+
response: {
|
|
7347
|
+
result: parseJSONValue(message.content)
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
}
|
|
7351
|
+
],
|
|
7352
|
+
role: "user"
|
|
7353
|
+
};
|
|
7354
|
+
}
|
|
7355
|
+
const toolCalls = getMessageToolCalls(message);
|
|
7356
|
+
if (message.role === "assistant" && toolCalls.length) {
|
|
7357
|
+
return {
|
|
7358
|
+
parts: [
|
|
7359
|
+
...message.content ? [
|
|
7360
|
+
{
|
|
7361
|
+
text: message.content
|
|
7362
|
+
}
|
|
7363
|
+
] : [],
|
|
7364
|
+
...toolCalls.map((toolCall) => ({
|
|
7365
|
+
functionCall: {
|
|
7366
|
+
args: toolCall.args,
|
|
7367
|
+
id: toolCall.id,
|
|
7368
|
+
name: toolCall.name
|
|
7369
|
+
}
|
|
7370
|
+
}))
|
|
7371
|
+
],
|
|
7372
|
+
role: "model"
|
|
7373
|
+
};
|
|
7374
|
+
}
|
|
7375
|
+
return {
|
|
7376
|
+
parts: [
|
|
7377
|
+
{
|
|
7378
|
+
text: message.content
|
|
7379
|
+
}
|
|
7380
|
+
],
|
|
7381
|
+
role: message.role === "assistant" ? "model" : "user"
|
|
7382
|
+
};
|
|
7383
|
+
};
|
|
7384
|
+
var extractText = (response) => {
|
|
7385
|
+
if (typeof response.output_text === "string") {
|
|
7386
|
+
return response.output_text;
|
|
7387
|
+
}
|
|
7388
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
7389
|
+
for (const item of output) {
|
|
7390
|
+
if (!item || typeof item !== "object") {
|
|
7391
|
+
continue;
|
|
7392
|
+
}
|
|
7393
|
+
const record = item;
|
|
7394
|
+
const content = Array.isArray(record.content) ? record.content : [];
|
|
7395
|
+
for (const contentItem of content) {
|
|
7396
|
+
if (!contentItem || typeof contentItem !== "object") {
|
|
7397
|
+
continue;
|
|
7398
|
+
}
|
|
7399
|
+
const contentRecord = contentItem;
|
|
7400
|
+
if (typeof contentRecord.text === "string") {
|
|
7401
|
+
return contentRecord.text;
|
|
7402
|
+
}
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
return "";
|
|
7406
|
+
};
|
|
7407
|
+
var extractToolCalls = (response) => {
|
|
7408
|
+
const output = Array.isArray(response.output) ? response.output : [];
|
|
7409
|
+
const toolCalls = [];
|
|
7410
|
+
for (const item of output) {
|
|
7411
|
+
if (!item || typeof item !== "object") {
|
|
7412
|
+
continue;
|
|
7413
|
+
}
|
|
7414
|
+
const record = item;
|
|
7415
|
+
if (record.type !== "function_call" || typeof record.name !== "string") {
|
|
7416
|
+
continue;
|
|
7417
|
+
}
|
|
7418
|
+
const args = typeof record.arguments === "string" ? parseJSON(record.arguments) : {};
|
|
7419
|
+
toolCalls.push({
|
|
7420
|
+
args,
|
|
7421
|
+
id: typeof record.call_id === "string" ? record.call_id : typeof record.id === "string" ? record.id : undefined,
|
|
7422
|
+
name: record.name
|
|
7423
|
+
});
|
|
7424
|
+
}
|
|
7425
|
+
return toolCalls;
|
|
7426
|
+
};
|
|
7427
|
+
var createOpenAIVoiceAssistantModel = (options) => {
|
|
7428
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
7429
|
+
const baseUrl = options.baseUrl ?? "https://api.openai.com/v1";
|
|
7430
|
+
const model = options.model ?? "gpt-4.1-mini";
|
|
7431
|
+
return {
|
|
7432
|
+
generate: async (input) => {
|
|
7433
|
+
const response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/responses`, {
|
|
7434
|
+
body: JSON.stringify({
|
|
7435
|
+
input: messagesToOpenAIInput(input.messages),
|
|
7436
|
+
instructions: [
|
|
7437
|
+
input.system,
|
|
7438
|
+
"Return a JSON object with assistantText, complete, transfer, escalate, voicemail, noAnswer, and result when you are not calling tools."
|
|
7439
|
+
].filter(Boolean).join(`
|
|
7440
|
+
|
|
7441
|
+
`),
|
|
7442
|
+
max_output_tokens: options.maxOutputTokens,
|
|
7443
|
+
model,
|
|
7444
|
+
temperature: options.temperature,
|
|
7445
|
+
text: {
|
|
7446
|
+
format: {
|
|
7447
|
+
name: "voice_route_result",
|
|
7448
|
+
schema: OUTPUT_SCHEMA,
|
|
7449
|
+
strict: false,
|
|
7450
|
+
type: "json_schema"
|
|
7451
|
+
}
|
|
7452
|
+
},
|
|
7453
|
+
tool_choice: input.tools.length ? "auto" : "none",
|
|
7454
|
+
tools: input.tools.map((tool) => ({
|
|
7455
|
+
description: tool.description,
|
|
7456
|
+
name: tool.name,
|
|
7457
|
+
parameters: tool.parameters ?? {
|
|
7458
|
+
additionalProperties: true,
|
|
7459
|
+
type: "object"
|
|
7460
|
+
},
|
|
7461
|
+
strict: false,
|
|
7462
|
+
type: "function"
|
|
7463
|
+
}))
|
|
7464
|
+
}),
|
|
7465
|
+
headers: {
|
|
7466
|
+
authorization: `Bearer ${options.apiKey}`,
|
|
7467
|
+
"content-type": "application/json"
|
|
7468
|
+
},
|
|
7469
|
+
method: "POST"
|
|
7470
|
+
});
|
|
7471
|
+
if (!response.ok) {
|
|
7472
|
+
throw createHTTPError("OpenAI", response);
|
|
7473
|
+
}
|
|
7474
|
+
const body = await response.json();
|
|
7475
|
+
if (body.usage && typeof body.usage === "object") {
|
|
7476
|
+
await options.onUsage?.(body.usage);
|
|
7477
|
+
}
|
|
7478
|
+
const toolCalls = extractToolCalls(body);
|
|
7479
|
+
if (toolCalls.length) {
|
|
7480
|
+
return {
|
|
7481
|
+
toolCalls
|
|
7482
|
+
};
|
|
7483
|
+
}
|
|
7484
|
+
return normalizeRouteOutput(parseJSON(extractText(body)));
|
|
7485
|
+
}
|
|
7486
|
+
};
|
|
7487
|
+
};
|
|
7488
|
+
var extractAnthropicText = (response) => {
|
|
7489
|
+
const content = Array.isArray(response.content) ? response.content : [];
|
|
7490
|
+
return content.map((item) => item && typeof item === "object" && item.type === "text" && typeof item.text === "string" ? item.text : "").filter(Boolean).join(`
|
|
7491
|
+
`);
|
|
7492
|
+
};
|
|
7493
|
+
var extractAnthropicToolCalls = (response) => {
|
|
7494
|
+
const content = Array.isArray(response.content) ? response.content : [];
|
|
7495
|
+
const toolCalls = [];
|
|
7496
|
+
for (const item of content) {
|
|
7497
|
+
if (!item || typeof item !== "object") {
|
|
7498
|
+
continue;
|
|
7499
|
+
}
|
|
7500
|
+
const record = item;
|
|
7501
|
+
if (record.type !== "tool_use" || typeof record.name !== "string") {
|
|
7502
|
+
continue;
|
|
7503
|
+
}
|
|
7504
|
+
toolCalls.push({
|
|
7505
|
+
args: record.input && typeof record.input === "object" ? record.input : {},
|
|
7506
|
+
id: typeof record.id === "string" ? record.id : undefined,
|
|
7507
|
+
name: record.name
|
|
7508
|
+
});
|
|
7509
|
+
}
|
|
7510
|
+
return toolCalls;
|
|
7511
|
+
};
|
|
7512
|
+
var createAnthropicVoiceAssistantModel = (options) => {
|
|
7513
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
7514
|
+
const baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
|
|
7515
|
+
const model = options.model ?? "claude-sonnet-4-5";
|
|
7516
|
+
return {
|
|
7517
|
+
generate: async (input) => {
|
|
7518
|
+
const response = await fetchImpl(`${baseUrl.replace(/\/$/, "")}/messages`, {
|
|
7519
|
+
body: JSON.stringify({
|
|
7520
|
+
max_tokens: options.maxOutputTokens ?? 1024,
|
|
7521
|
+
messages: input.messages.map(messageToAnthropicMessage).filter(Boolean),
|
|
7522
|
+
model,
|
|
7523
|
+
system: [input.system, ROUTE_RESULT_INSTRUCTION].filter(Boolean).join(`
|
|
7524
|
+
|
|
7525
|
+
`),
|
|
7526
|
+
temperature: options.temperature,
|
|
7527
|
+
tool_choice: input.tools.length ? { type: "auto" } : { type: "none" },
|
|
7528
|
+
tools: input.tools.map((tool) => ({
|
|
7529
|
+
description: tool.description,
|
|
7530
|
+
input_schema: tool.parameters ?? {
|
|
7531
|
+
additionalProperties: true,
|
|
7532
|
+
type: "object"
|
|
7533
|
+
},
|
|
7534
|
+
name: tool.name
|
|
7535
|
+
}))
|
|
7536
|
+
}),
|
|
7537
|
+
headers: {
|
|
7538
|
+
"anthropic-version": options.version ?? "2023-06-01",
|
|
7539
|
+
"content-type": "application/json",
|
|
7540
|
+
"x-api-key": options.apiKey
|
|
7541
|
+
},
|
|
7542
|
+
method: "POST"
|
|
7543
|
+
});
|
|
7544
|
+
if (!response.ok) {
|
|
7545
|
+
throw createHTTPError("Anthropic", response);
|
|
7546
|
+
}
|
|
7547
|
+
const body = await response.json();
|
|
7548
|
+
if (body.usage && typeof body.usage === "object") {
|
|
7549
|
+
await options.onUsage?.(body.usage);
|
|
7550
|
+
}
|
|
7551
|
+
const toolCalls = extractAnthropicToolCalls(body);
|
|
7552
|
+
if (toolCalls.length) {
|
|
7553
|
+
return {
|
|
7554
|
+
assistantText: extractAnthropicText(body) || undefined,
|
|
7555
|
+
toolCalls
|
|
7556
|
+
};
|
|
7557
|
+
}
|
|
7558
|
+
return normalizeRouteOutput(parseJSON(extractAnthropicText(body)));
|
|
7559
|
+
}
|
|
7560
|
+
};
|
|
7561
|
+
};
|
|
7562
|
+
var extractGeminiCandidateParts = (response) => {
|
|
7563
|
+
const candidates = Array.isArray(response.candidates) ? response.candidates : [];
|
|
7564
|
+
const first = candidates[0];
|
|
7565
|
+
if (!first || typeof first !== "object") {
|
|
7566
|
+
return [];
|
|
7567
|
+
}
|
|
7568
|
+
const content = first.content;
|
|
7569
|
+
if (!content || typeof content !== "object") {
|
|
7570
|
+
return [];
|
|
7571
|
+
}
|
|
7572
|
+
const parts = content.parts;
|
|
7573
|
+
return Array.isArray(parts) ? parts : [];
|
|
7574
|
+
};
|
|
7575
|
+
var extractGeminiText = (response) => extractGeminiCandidateParts(response).map((part) => part && typeof part === "object" && typeof part.text === "string" ? part.text : "").filter(Boolean).join(`
|
|
7576
|
+
`);
|
|
7577
|
+
var extractGeminiToolCalls = (response) => {
|
|
7578
|
+
const toolCalls = [];
|
|
7579
|
+
for (const part of extractGeminiCandidateParts(response)) {
|
|
7580
|
+
if (!part || typeof part !== "object") {
|
|
7581
|
+
continue;
|
|
7582
|
+
}
|
|
7583
|
+
const functionCall = part.functionCall;
|
|
7584
|
+
if (!functionCall || typeof functionCall !== "object") {
|
|
7585
|
+
continue;
|
|
7586
|
+
}
|
|
7587
|
+
const record = functionCall;
|
|
7588
|
+
if (typeof record.name !== "string") {
|
|
7589
|
+
continue;
|
|
7590
|
+
}
|
|
7591
|
+
toolCalls.push({
|
|
7592
|
+
args: record.args && typeof record.args === "object" ? record.args : {},
|
|
7593
|
+
id: typeof record.id === "string" ? record.id : undefined,
|
|
7594
|
+
name: record.name
|
|
7595
|
+
});
|
|
7596
|
+
}
|
|
7597
|
+
return toolCalls;
|
|
7598
|
+
};
|
|
7599
|
+
var createGeminiVoiceAssistantModel = (options) => {
|
|
7600
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
7601
|
+
const baseUrl = options.baseUrl ?? "https://generativelanguage.googleapis.com/v1beta";
|
|
7602
|
+
const model = options.model ?? "gemini-2.5-flash";
|
|
7603
|
+
const maxRetries = Math.max(0, options.maxRetries ?? 2);
|
|
7604
|
+
return {
|
|
7605
|
+
generate: async (input) => {
|
|
7606
|
+
const endpoint = `${baseUrl.replace(/\/$/, "")}/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(options.apiKey)}`;
|
|
7607
|
+
let response;
|
|
7608
|
+
for (let attempt = 0;attempt <= maxRetries; attempt += 1) {
|
|
7609
|
+
response = await fetchImpl(endpoint, {
|
|
7610
|
+
body: JSON.stringify({
|
|
7611
|
+
contents: input.messages.map(messageToGeminiContent).filter(Boolean),
|
|
7612
|
+
generationConfig: {
|
|
7613
|
+
maxOutputTokens: options.maxOutputTokens,
|
|
7614
|
+
...input.tools.length ? {} : {
|
|
7615
|
+
responseMimeType: "application/json",
|
|
7616
|
+
responseSchema: toGeminiSchema(OUTPUT_SCHEMA)
|
|
7617
|
+
},
|
|
7618
|
+
temperature: options.temperature
|
|
7619
|
+
},
|
|
7620
|
+
systemInstruction: {
|
|
7621
|
+
parts: [
|
|
7622
|
+
{
|
|
7623
|
+
text: [input.system, ROUTE_RESULT_INSTRUCTION].filter(Boolean).join(`
|
|
7624
|
+
|
|
7625
|
+
`)
|
|
7626
|
+
}
|
|
7627
|
+
]
|
|
7628
|
+
},
|
|
7629
|
+
tools: input.tools.length ? [
|
|
7630
|
+
{
|
|
7631
|
+
functionDeclarations: input.tools.map((tool) => ({
|
|
7632
|
+
description: tool.description,
|
|
7633
|
+
name: tool.name,
|
|
7634
|
+
parameters: toGeminiSchema(tool.parameters ?? {
|
|
7635
|
+
additionalProperties: true,
|
|
7636
|
+
type: "object"
|
|
7637
|
+
})
|
|
7638
|
+
}))
|
|
7639
|
+
}
|
|
7640
|
+
] : undefined
|
|
7641
|
+
}),
|
|
7642
|
+
headers: {
|
|
7643
|
+
"content-type": "application/json"
|
|
7644
|
+
},
|
|
7645
|
+
method: "POST"
|
|
7646
|
+
});
|
|
7647
|
+
if (response.ok || response.status !== 429 && response.status < 500 || attempt === maxRetries) {
|
|
7648
|
+
break;
|
|
7649
|
+
}
|
|
7650
|
+
const retryAfter = Number(response.headers.get("retry-after"));
|
|
7651
|
+
await sleep4(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt);
|
|
7652
|
+
}
|
|
7653
|
+
if (!response) {
|
|
7654
|
+
throw new Error("Gemini voice assistant model failed: no response");
|
|
7655
|
+
}
|
|
7656
|
+
if (!response.ok) {
|
|
7657
|
+
throw createHTTPError("Gemini", response);
|
|
7658
|
+
}
|
|
7659
|
+
const body = await response.json();
|
|
7660
|
+
if (body.usageMetadata && typeof body.usageMetadata === "object") {
|
|
7661
|
+
await options.onUsage?.(body.usageMetadata);
|
|
7662
|
+
}
|
|
7663
|
+
const toolCalls = extractGeminiToolCalls(body);
|
|
7664
|
+
if (toolCalls.length) {
|
|
7665
|
+
return {
|
|
7666
|
+
assistantText: extractGeminiText(body) || undefined,
|
|
7667
|
+
toolCalls
|
|
7668
|
+
};
|
|
7669
|
+
}
|
|
7670
|
+
return normalizeRouteOutput(parseJSON(extractGeminiText(body)));
|
|
7671
|
+
}
|
|
7672
|
+
};
|
|
7673
|
+
};
|
|
6315
7674
|
// src/sqliteStore.ts
|
|
6316
7675
|
import { Database } from "bun:sqlite";
|
|
6317
7676
|
var normalizeTableNameSegment = (value) => value.trim().replace(/[^a-zA-Z0-9_]+/g, "_").replace(/^_+|_+$/g, "") || "voice";
|
|
@@ -7987,230 +9346,6 @@ var resolveVoiceOpsPreset = (name, overrides = {}) => {
|
|
|
7987
9346
|
taskPolicies: mergePolicies(preset.taskPolicies, overrides.taskPolicies)
|
|
7988
9347
|
};
|
|
7989
9348
|
};
|
|
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
9349
|
// src/correction.ts
|
|
8215
9350
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8216
9351
|
var buildAliasMatcher = (alias) => new RegExp(`(?<![\\p{L}\\p{N}'])${escapeRegExp(alias)}(?![\\p{L}\\p{N}'])`, "giu");
|
|
@@ -8990,6 +10125,7 @@ export {
|
|
|
8990
10125
|
summarizeVoiceOpsTaskQueue,
|
|
8991
10126
|
summarizeVoiceOpsTaskAnalytics,
|
|
8992
10127
|
summarizeVoiceIntegrationEvents,
|
|
10128
|
+
summarizeVoiceAssistantRuns,
|
|
8993
10129
|
startVoiceOpsTask,
|
|
8994
10130
|
shapeTelephonyAssistantText,
|
|
8995
10131
|
selectVoiceTraceEventsForPrune,
|
|
@@ -9001,6 +10137,7 @@ export {
|
|
|
9001
10137
|
resolveVoiceOpsTaskAssignment,
|
|
9002
10138
|
resolveVoiceOpsTaskAgeBucket,
|
|
9003
10139
|
resolveVoiceOpsPreset,
|
|
10140
|
+
resolveVoiceAssistantMemoryNamespace,
|
|
9004
10141
|
resolveTurnDetectionConfig,
|
|
9005
10142
|
resolveAudioConditioningConfig,
|
|
9006
10143
|
requeueVoiceOpsTask,
|
|
@@ -9076,6 +10213,7 @@ export {
|
|
|
9076
10213
|
createVoiceMemoryTraceSinkDeliveryStore,
|
|
9077
10214
|
createVoiceMemoryTraceEventStore,
|
|
9078
10215
|
createVoiceMemoryStore,
|
|
10216
|
+
createVoiceMemoryAssistantMemoryStore,
|
|
9079
10217
|
createVoiceLinearIssueUpdateSink,
|
|
9080
10218
|
createVoiceLinearIssueSyncSinks,
|
|
9081
10219
|
createVoiceLinearIssueSink,
|
|
@@ -9095,13 +10233,18 @@ export {
|
|
|
9095
10233
|
createVoiceFileReviewStore,
|
|
9096
10234
|
createVoiceFileIntegrationEventStore,
|
|
9097
10235
|
createVoiceFileExternalObjectMapStore,
|
|
10236
|
+
createVoiceFileAssistantMemoryStore,
|
|
9098
10237
|
createVoiceExternalObjectMapId,
|
|
9099
10238
|
createVoiceExternalObjectMap,
|
|
10239
|
+
createVoiceExperiment,
|
|
9100
10240
|
createVoiceCallReviewRecorder,
|
|
9101
10241
|
createVoiceCallReviewFromSession,
|
|
9102
10242
|
createVoiceCallReviewFromLiveTelephonyReport,
|
|
9103
10243
|
createVoiceCallCompletedEvent,
|
|
9104
10244
|
createVoiceCRMActivitySink,
|
|
10245
|
+
createVoiceAssistantMemoryRecord,
|
|
10246
|
+
createVoiceAssistantMemoryHandle,
|
|
10247
|
+
createVoiceAssistant,
|
|
9105
10248
|
createVoiceAgentTool,
|
|
9106
10249
|
createVoiceAgentSquad,
|
|
9107
10250
|
createVoiceAgent,
|
|
@@ -9113,9 +10256,13 @@ export {
|
|
|
9113
10256
|
createStoredVoiceCallReviewArtifact,
|
|
9114
10257
|
createRiskyTurnCorrectionHandler,
|
|
9115
10258
|
createPhraseHintCorrectionHandler,
|
|
10259
|
+
createOpenAIVoiceAssistantModel,
|
|
10260
|
+
createJSONVoiceAssistantModel,
|
|
9116
10261
|
createId,
|
|
10262
|
+
createGeminiVoiceAssistantModel,
|
|
9117
10263
|
createDomainPhraseHints,
|
|
9118
10264
|
createDomainLexicon,
|
|
10265
|
+
createAnthropicVoiceAssistantModel,
|
|
9119
10266
|
conditionAudioChunk,
|
|
9120
10267
|
completeVoiceOpsTask,
|
|
9121
10268
|
claimVoiceOpsTask,
|