@absolutejs/voice 0.0.22-beta.107 → 0.0.22-beta.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/campaign.d.ts +449 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +384 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10546,12 +10546,301 @@ var createVoiceAppKitRoutes = (options) => {
|
|
|
10546
10546
|
};
|
|
10547
10547
|
};
|
|
10548
10548
|
var createVoiceAppKit = createVoiceAppKitRoutes;
|
|
10549
|
+
// src/campaign.ts
|
|
10550
|
+
import { Elysia as Elysia17 } from "elysia";
|
|
10551
|
+
var createId2 = () => crypto.randomUUID();
|
|
10552
|
+
var cloneRecord = (record) => ({
|
|
10553
|
+
attempts: record.attempts.map((attempt) => ({ ...attempt })),
|
|
10554
|
+
campaign: { ...record.campaign },
|
|
10555
|
+
recipients: record.recipients.map((recipient) => ({ ...recipient }))
|
|
10556
|
+
});
|
|
10557
|
+
var ensureRecord = async (store, campaignId) => {
|
|
10558
|
+
const record = await store.get(campaignId);
|
|
10559
|
+
if (!record) {
|
|
10560
|
+
throw new Error(`Voice campaign ${campaignId} was not found.`);
|
|
10561
|
+
}
|
|
10562
|
+
return cloneRecord(record);
|
|
10563
|
+
};
|
|
10564
|
+
var saveRecord = async (store, record) => {
|
|
10565
|
+
record.campaign.updatedAt = Date.now();
|
|
10566
|
+
await store.set(record.campaign.id, record);
|
|
10567
|
+
return record;
|
|
10568
|
+
};
|
|
10569
|
+
var activeAttemptCount = (record) => record.attempts.filter((attempt) => attempt.status === "queued" || attempt.status === "running").length;
|
|
10570
|
+
var maybeCompleteCampaign = (record) => {
|
|
10571
|
+
const unfinished = record.recipients.some((recipient) => recipient.status === "pending" || recipient.status === "queued");
|
|
10572
|
+
const active = activeAttemptCount(record) > 0;
|
|
10573
|
+
if (!unfinished && !active && record.campaign.status === "running") {
|
|
10574
|
+
record.campaign.status = "completed";
|
|
10575
|
+
}
|
|
10576
|
+
};
|
|
10577
|
+
var createVoiceMemoryCampaignStore = () => {
|
|
10578
|
+
const campaigns = new Map;
|
|
10579
|
+
return {
|
|
10580
|
+
get: (id) => {
|
|
10581
|
+
const record = campaigns.get(id);
|
|
10582
|
+
return record ? cloneRecord(record) : undefined;
|
|
10583
|
+
},
|
|
10584
|
+
list: () => [...campaigns.values()].map(cloneRecord).sort((left, right) => right.campaign.createdAt - left.campaign.createdAt),
|
|
10585
|
+
remove: (id) => {
|
|
10586
|
+
campaigns.delete(id);
|
|
10587
|
+
},
|
|
10588
|
+
set: (id, record) => {
|
|
10589
|
+
campaigns.set(id, cloneRecord(record));
|
|
10590
|
+
}
|
|
10591
|
+
};
|
|
10592
|
+
};
|
|
10593
|
+
var summarizeVoiceCampaigns = (records) => {
|
|
10594
|
+
const summary = {
|
|
10595
|
+
attempts: { failed: 0, queued: 0, running: 0, succeeded: 0, total: 0 },
|
|
10596
|
+
campaigns: {
|
|
10597
|
+
canceled: 0,
|
|
10598
|
+
completed: 0,
|
|
10599
|
+
draft: 0,
|
|
10600
|
+
paused: 0,
|
|
10601
|
+
running: 0,
|
|
10602
|
+
total: records.length
|
|
10603
|
+
},
|
|
10604
|
+
recipients: {
|
|
10605
|
+
canceled: 0,
|
|
10606
|
+
completed: 0,
|
|
10607
|
+
failed: 0,
|
|
10608
|
+
pending: 0,
|
|
10609
|
+
queued: 0,
|
|
10610
|
+
total: 0
|
|
10611
|
+
}
|
|
10612
|
+
};
|
|
10613
|
+
for (const record of records) {
|
|
10614
|
+
summary.campaigns[record.campaign.status] += 1;
|
|
10615
|
+
for (const recipient of record.recipients) {
|
|
10616
|
+
summary.recipients.total += 1;
|
|
10617
|
+
summary.recipients[recipient.status] += 1;
|
|
10618
|
+
}
|
|
10619
|
+
for (const attempt of record.attempts) {
|
|
10620
|
+
summary.attempts.total += 1;
|
|
10621
|
+
if (attempt.status === "queued" || attempt.status === "running") {
|
|
10622
|
+
summary.attempts[attempt.status] += 1;
|
|
10623
|
+
}
|
|
10624
|
+
if (attempt.status === "failed" || attempt.status === "succeeded") {
|
|
10625
|
+
summary.attempts[attempt.status] += 1;
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
return summary;
|
|
10630
|
+
};
|
|
10631
|
+
var createVoiceCampaign = (options) => {
|
|
10632
|
+
const { store } = options;
|
|
10633
|
+
return {
|
|
10634
|
+
addRecipients: async (campaignId, recipients) => {
|
|
10635
|
+
const record = await ensureRecord(store, campaignId);
|
|
10636
|
+
const at = Date.now();
|
|
10637
|
+
record.recipients.push(...recipients.map((recipient) => ({
|
|
10638
|
+
attempts: 0,
|
|
10639
|
+
createdAt: at,
|
|
10640
|
+
id: recipient.id ?? createId2(),
|
|
10641
|
+
metadata: recipient.metadata,
|
|
10642
|
+
name: recipient.name,
|
|
10643
|
+
phone: recipient.phone,
|
|
10644
|
+
status: "pending",
|
|
10645
|
+
updatedAt: at,
|
|
10646
|
+
variables: recipient.variables
|
|
10647
|
+
})));
|
|
10648
|
+
return saveRecord(store, record);
|
|
10649
|
+
},
|
|
10650
|
+
cancel: async (campaignId) => {
|
|
10651
|
+
const record = await ensureRecord(store, campaignId);
|
|
10652
|
+
const at = Date.now();
|
|
10653
|
+
record.campaign.status = "canceled";
|
|
10654
|
+
for (const recipient of record.recipients) {
|
|
10655
|
+
if (recipient.status === "pending" || recipient.status === "queued") {
|
|
10656
|
+
recipient.status = "canceled";
|
|
10657
|
+
recipient.updatedAt = at;
|
|
10658
|
+
}
|
|
10659
|
+
}
|
|
10660
|
+
for (const attempt of record.attempts) {
|
|
10661
|
+
if (attempt.status === "queued" || attempt.status === "running") {
|
|
10662
|
+
attempt.status = "canceled";
|
|
10663
|
+
attempt.completedAt = at;
|
|
10664
|
+
attempt.updatedAt = at;
|
|
10665
|
+
}
|
|
10666
|
+
}
|
|
10667
|
+
return saveRecord(store, record);
|
|
10668
|
+
},
|
|
10669
|
+
completeAttempt: async (campaignId, attemptId, result) => {
|
|
10670
|
+
const record = await ensureRecord(store, campaignId);
|
|
10671
|
+
const attempt = record.attempts.find((item) => item.id === attemptId);
|
|
10672
|
+
if (!attempt) {
|
|
10673
|
+
throw new Error(`Voice campaign attempt ${attemptId} was not found.`);
|
|
10674
|
+
}
|
|
10675
|
+
const recipient = record.recipients.find((item) => item.id === attempt.recipientId);
|
|
10676
|
+
const at = Date.now();
|
|
10677
|
+
attempt.completedAt = at;
|
|
10678
|
+
attempt.error = result.error;
|
|
10679
|
+
attempt.externalCallId = result.externalCallId ?? attempt.externalCallId;
|
|
10680
|
+
attempt.metadata = { ...attempt.metadata, ...result.metadata };
|
|
10681
|
+
attempt.status = result.status === "succeeded" ? "succeeded" : "failed";
|
|
10682
|
+
attempt.updatedAt = at;
|
|
10683
|
+
if (recipient) {
|
|
10684
|
+
recipient.completedAt = result.status === "succeeded" ? at : undefined;
|
|
10685
|
+
recipient.error = result.error;
|
|
10686
|
+
recipient.status = result.status === "succeeded" ? "completed" : recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
|
|
10687
|
+
recipient.updatedAt = at;
|
|
10688
|
+
}
|
|
10689
|
+
maybeCompleteCampaign(record);
|
|
10690
|
+
return saveRecord(store, record);
|
|
10691
|
+
},
|
|
10692
|
+
create: async (input) => {
|
|
10693
|
+
const at = Date.now();
|
|
10694
|
+
const campaign = {
|
|
10695
|
+
createdAt: at,
|
|
10696
|
+
description: input.description,
|
|
10697
|
+
id: input.id ?? createId2(),
|
|
10698
|
+
maxAttempts: input.maxAttempts ?? 1,
|
|
10699
|
+
maxConcurrentAttempts: input.maxConcurrentAttempts ?? 1,
|
|
10700
|
+
metadata: input.metadata,
|
|
10701
|
+
name: input.name,
|
|
10702
|
+
status: "draft",
|
|
10703
|
+
updatedAt: at
|
|
10704
|
+
};
|
|
10705
|
+
const record = { attempts: [], campaign, recipients: [] };
|
|
10706
|
+
await store.set(campaign.id, record);
|
|
10707
|
+
return record;
|
|
10708
|
+
},
|
|
10709
|
+
enqueue: async (campaignId) => {
|
|
10710
|
+
const record = await ensureRecord(store, campaignId);
|
|
10711
|
+
const at = Date.now();
|
|
10712
|
+
record.campaign.status = "running";
|
|
10713
|
+
for (const recipient of record.recipients) {
|
|
10714
|
+
if (recipient.status === "pending") {
|
|
10715
|
+
recipient.status = "queued";
|
|
10716
|
+
recipient.updatedAt = at;
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
return saveRecord(store, record);
|
|
10720
|
+
},
|
|
10721
|
+
get: async (campaignId) => await store.get(campaignId),
|
|
10722
|
+
list: async () => await store.list(),
|
|
10723
|
+
pause: async (campaignId) => {
|
|
10724
|
+
const record = await ensureRecord(store, campaignId);
|
|
10725
|
+
record.campaign.status = "paused";
|
|
10726
|
+
return saveRecord(store, record);
|
|
10727
|
+
},
|
|
10728
|
+
remove: async (campaignId) => {
|
|
10729
|
+
await store.remove(campaignId);
|
|
10730
|
+
},
|
|
10731
|
+
resume: async (campaignId) => {
|
|
10732
|
+
const record = await ensureRecord(store, campaignId);
|
|
10733
|
+
record.campaign.status = "running";
|
|
10734
|
+
return saveRecord(store, record);
|
|
10735
|
+
},
|
|
10736
|
+
summarize: async () => summarizeVoiceCampaigns(await store.list()),
|
|
10737
|
+
tick: async (campaignId) => {
|
|
10738
|
+
const record = await ensureRecord(store, campaignId);
|
|
10739
|
+
const result = {
|
|
10740
|
+
attempted: 0,
|
|
10741
|
+
campaignId,
|
|
10742
|
+
errors: [],
|
|
10743
|
+
started: []
|
|
10744
|
+
};
|
|
10745
|
+
if (record.campaign.status !== "running") {
|
|
10746
|
+
return result;
|
|
10747
|
+
}
|
|
10748
|
+
const capacity = Math.max(0, record.campaign.maxConcurrentAttempts - activeAttemptCount(record));
|
|
10749
|
+
const candidates = record.recipients.filter((recipient) => recipient.status === "queued" && recipient.attempts < record.campaign.maxAttempts).slice(0, capacity);
|
|
10750
|
+
const at = Date.now();
|
|
10751
|
+
for (const recipient of candidates) {
|
|
10752
|
+
const attempt = {
|
|
10753
|
+
campaignId,
|
|
10754
|
+
createdAt: at,
|
|
10755
|
+
id: createId2(),
|
|
10756
|
+
recipientId: recipient.id,
|
|
10757
|
+
startedAt: at,
|
|
10758
|
+
status: options.dialer ? "running" : "queued",
|
|
10759
|
+
updatedAt: at
|
|
10760
|
+
};
|
|
10761
|
+
recipient.attempts += 1;
|
|
10762
|
+
recipient.updatedAt = at;
|
|
10763
|
+
record.attempts.push(attempt);
|
|
10764
|
+
result.started.push(attempt);
|
|
10765
|
+
result.attempted += 1;
|
|
10766
|
+
if (options.dialer) {
|
|
10767
|
+
try {
|
|
10768
|
+
const dialerResult = await options.dialer({
|
|
10769
|
+
attempt,
|
|
10770
|
+
campaign: record.campaign,
|
|
10771
|
+
recipient
|
|
10772
|
+
});
|
|
10773
|
+
attempt.externalCallId = dialerResult.externalCallId;
|
|
10774
|
+
attempt.metadata = dialerResult.metadata;
|
|
10775
|
+
attempt.status = dialerResult.status ?? "running";
|
|
10776
|
+
attempt.updatedAt = Date.now();
|
|
10777
|
+
} catch (error) {
|
|
10778
|
+
attempt.completedAt = Date.now();
|
|
10779
|
+
attempt.error = error instanceof Error ? error.message : String(error);
|
|
10780
|
+
attempt.status = "failed";
|
|
10781
|
+
attempt.updatedAt = Date.now();
|
|
10782
|
+
recipient.error = attempt.error;
|
|
10783
|
+
recipient.status = recipient.attempts >= record.campaign.maxAttempts ? "failed" : "pending";
|
|
10784
|
+
result.errors.push({
|
|
10785
|
+
error: attempt.error,
|
|
10786
|
+
recipientId: recipient.id
|
|
10787
|
+
});
|
|
10788
|
+
}
|
|
10789
|
+
}
|
|
10790
|
+
}
|
|
10791
|
+
maybeCompleteCampaign(record);
|
|
10792
|
+
await saveRecord(store, record);
|
|
10793
|
+
return result;
|
|
10794
|
+
}
|
|
10795
|
+
};
|
|
10796
|
+
};
|
|
10797
|
+
var escapeHtml18 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10798
|
+
var renderVoiceCampaignsHTML = (records, options = {}) => {
|
|
10799
|
+
const title = options.title ?? "Voice Campaigns";
|
|
10800
|
+
const rows = records.map((record) => `<tr><td>${escapeHtml18(record.campaign.name)}</td><td>${escapeHtml18(record.campaign.status)}</td><td>${String(record.recipients.length)}</td><td>${String(record.attempts.length)}</td><td>${new Date(record.campaign.updatedAt).toLocaleString()}</td></tr>`).join("");
|
|
10801
|
+
const summary = summarizeVoiceCampaigns(records);
|
|
10802
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml18(title)}</title><style>body{background:#111827;color:#f9fafb;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(251,146,60,.18),rgba(45,212,191,.12));border:1px solid #334155;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#fdba74;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.grid{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.grid article,table{background:#172033;border:1px solid #334155;border-radius:18px}.grid article{padding:16px}.grid span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #334155;padding:12px;text-align:left}</style></head><body><main><section class="hero"><p class="eyebrow">Self-hosted outbound</p><h1>${escapeHtml18(title)}</h1><p>Campaign orchestration, recipients, attempts, retries, and outcomes without a hosted dialer dashboard.</p><section class="grid"><article><span>Campaigns</span><strong>${String(summary.campaigns.total)}</strong></article><article><span>Recipients</span><strong>${String(summary.recipients.total)}</strong></article><article><span>Attempts</span><strong>${String(summary.attempts.total)}</strong></article><article><span>Running</span><strong>${String(summary.campaigns.running)}</strong></article></section></section><table><thead><tr><th>Name</th><th>Status</th><th>Recipients</th><th>Attempts</th><th>Updated</th></tr></thead><tbody>${rows || '<tr><td colspan="5">No campaigns yet.</td></tr>'}</tbody></table></main></body></html>`;
|
|
10803
|
+
};
|
|
10804
|
+
var readJsonBody = async (request) => {
|
|
10805
|
+
const text = await request.text();
|
|
10806
|
+
return text.trim() ? JSON.parse(text) : {};
|
|
10807
|
+
};
|
|
10808
|
+
var createVoiceCampaignRoutes = (options) => {
|
|
10809
|
+
const runtime = createVoiceCampaign(options);
|
|
10810
|
+
const path = options.path ?? "/api/voice/campaigns";
|
|
10811
|
+
const htmlPath = options.htmlPath === undefined ? "/voice/campaigns" : options.htmlPath;
|
|
10812
|
+
const app = new Elysia17({ name: options.name ?? "absolutejs-voice-campaigns" }).get(path, async () => ({
|
|
10813
|
+
campaigns: await runtime.list(),
|
|
10814
|
+
summary: await runtime.summarize()
|
|
10815
|
+
})).post(path, async ({ request }) => runtime.create(await readJsonBody(request))).get(`${path}/:campaignId`, ({ params }) => runtime.get(params.campaignId)).delete(`${path}/:campaignId`, async ({ params }) => {
|
|
10816
|
+
await runtime.remove(params.campaignId);
|
|
10817
|
+
return { ok: true };
|
|
10818
|
+
}).post(`${path}/:campaignId/recipients`, async ({ params, request }) => {
|
|
10819
|
+
const body = await readJsonBody(request);
|
|
10820
|
+
return runtime.addRecipients(params.campaignId, body.recipients ?? []);
|
|
10821
|
+
}).post(`${path}/:campaignId/enqueue`, ({ params }) => runtime.enqueue(params.campaignId)).post(`${path}/:campaignId/pause`, ({ params }) => runtime.pause(params.campaignId)).post(`${path}/:campaignId/resume`, ({ params }) => runtime.resume(params.campaignId)).post(`${path}/:campaignId/cancel`, ({ params }) => runtime.cancel(params.campaignId)).post(`${path}/:campaignId/tick`, ({ params }) => runtime.tick(params.campaignId)).post(`${path}/:campaignId/attempts/:attemptId/result`, async ({
|
|
10822
|
+
params,
|
|
10823
|
+
request
|
|
10824
|
+
}) => runtime.completeAttempt(params.campaignId, params.attemptId, await readJsonBody(request)));
|
|
10825
|
+
if (htmlPath) {
|
|
10826
|
+
app.get(htmlPath, async () => {
|
|
10827
|
+
const records = await runtime.list();
|
|
10828
|
+
return new Response(renderVoiceCampaignsHTML(records, options), {
|
|
10829
|
+
headers: {
|
|
10830
|
+
"content-type": "text/html; charset=utf-8",
|
|
10831
|
+
...options.headers
|
|
10832
|
+
}
|
|
10833
|
+
});
|
|
10834
|
+
});
|
|
10835
|
+
}
|
|
10836
|
+
return app;
|
|
10837
|
+
};
|
|
10549
10838
|
// src/simulationSuite.ts
|
|
10550
|
-
import { Elysia as
|
|
10839
|
+
import { Elysia as Elysia20 } from "elysia";
|
|
10551
10840
|
|
|
10552
10841
|
// src/outcomeContract.ts
|
|
10553
|
-
import { Elysia as
|
|
10554
|
-
var
|
|
10842
|
+
import { Elysia as Elysia18 } from "elysia";
|
|
10843
|
+
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10555
10844
|
var getPayloadString = (event, key) => typeof event.payload[key] === "string" ? event.payload[key] : undefined;
|
|
10556
10845
|
var toList = async (input) => Array.isArray(input) ? input : await input?.list() ?? [];
|
|
10557
10846
|
var hydrateSessions = async (input) => {
|
|
@@ -10659,9 +10948,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10659
10948
|
const contracts = report.contracts.map((contract) => `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
10660
10949
|
<div class="contract-header">
|
|
10661
10950
|
<div>
|
|
10662
|
-
<p class="eyebrow">${
|
|
10663
|
-
<h2>${
|
|
10664
|
-
${contract.description ? `<p>${
|
|
10951
|
+
<p class="eyebrow">${escapeHtml19(contract.contractId)}</p>
|
|
10952
|
+
<h2>${escapeHtml19(contract.label ?? contract.contractId)}</h2>
|
|
10953
|
+
${contract.description ? `<p>${escapeHtml19(contract.description)}</p>` : ""}
|
|
10665
10954
|
</div>
|
|
10666
10955
|
<strong>${contract.pass ? "pass" : "fail"}</strong>
|
|
10667
10956
|
</div>
|
|
@@ -10672,9 +10961,9 @@ var renderVoiceOutcomeContractHTML = (report, options = {}) => {
|
|
|
10672
10961
|
<span>handoffs ${String(contract.matched.handoffs)}</span>
|
|
10673
10962
|
<span>events ${String(contract.matched.integrationEvents)}</span>
|
|
10674
10963
|
</div>
|
|
10675
|
-
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${
|
|
10964
|
+
${contract.issues.length ? `<ul>${contract.issues.map((issue) => `<li>${escapeHtml19(issue.message)}</li>`).join("")}</ul>` : ""}
|
|
10676
10965
|
</section>`).join("");
|
|
10677
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
10966
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml19(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(14,165,233,.12))}.eyebrow{color:#7dd3fc;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0}.summary,.grid{display:flex;flex-wrap:wrap;gap:10px}.pill,.grid span{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}li{margin:8px 0}@media(max-width:800px){main{padding:18px}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Business Outcome Verification</p><h1>${escapeHtml19(title)}</h1><div class="summary"><span class="pill ${report.status}">${report.status}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No outcome contracts configured.</p></section>'}</main></body></html>`;
|
|
10678
10967
|
};
|
|
10679
10968
|
var createVoiceOutcomeContractJSONHandler = (options) => async () => runVoiceOutcomeContractSuite(options);
|
|
10680
10969
|
var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
@@ -10690,7 +10979,7 @@ var createVoiceOutcomeContractHTMLHandler = (options) => async () => {
|
|
|
10690
10979
|
var createVoiceOutcomeContractRoutes = (options) => {
|
|
10691
10980
|
const path = options.path ?? "/api/outcome-contracts";
|
|
10692
10981
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
10693
|
-
const routes = new
|
|
10982
|
+
const routes = new Elysia18({
|
|
10694
10983
|
name: options.name ?? "absolutejs-voice-outcome-contracts"
|
|
10695
10984
|
}).get(path, createVoiceOutcomeContractJSONHandler(options));
|
|
10696
10985
|
if (htmlPath) {
|
|
@@ -10700,7 +10989,7 @@ var createVoiceOutcomeContractRoutes = (options) => {
|
|
|
10700
10989
|
};
|
|
10701
10990
|
|
|
10702
10991
|
// src/toolContract.ts
|
|
10703
|
-
import { Elysia as
|
|
10992
|
+
import { Elysia as Elysia19 } from "elysia";
|
|
10704
10993
|
|
|
10705
10994
|
// src/toolRuntime.ts
|
|
10706
10995
|
var toErrorMessage4 = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -10909,7 +11198,7 @@ var createDefaultTurn = (caseId) => ({
|
|
|
10909
11198
|
});
|
|
10910
11199
|
var defaultApi = {};
|
|
10911
11200
|
var sameJSON = (left, right) => JSON.stringify(left) === JSON.stringify(right);
|
|
10912
|
-
var
|
|
11201
|
+
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10913
11202
|
var evaluateExpectation = (input) => {
|
|
10914
11203
|
const issues = [];
|
|
10915
11204
|
const expect = input.expect;
|
|
@@ -11075,19 +11364,19 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11075
11364
|
const title = options.title ?? "Voice Tool Contracts";
|
|
11076
11365
|
const contracts = report.contracts.map((contract) => {
|
|
11077
11366
|
const cases = contract.cases.map((testCase) => `<tr>
|
|
11078
|
-
<td>${
|
|
11367
|
+
<td>${escapeHtml20(testCase.label ?? testCase.caseId)}</td>
|
|
11079
11368
|
<td class="${testCase.pass ? "pass" : "fail"}">${testCase.pass ? "pass" : "fail"}</td>
|
|
11080
|
-
<td>${
|
|
11369
|
+
<td>${escapeHtml20(testCase.status)}</td>
|
|
11081
11370
|
<td>${String(testCase.attempts)}</td>
|
|
11082
11371
|
<td>${String(testCase.elapsedMs)}ms</td>
|
|
11083
11372
|
<td>${testCase.timedOut ? "yes" : "no"}</td>
|
|
11084
|
-
<td>${
|
|
11373
|
+
<td>${escapeHtml20(testCase.issues.map((issue) => issue.message).join(" ") || testCase.error || "")}</td>
|
|
11085
11374
|
</tr>`).join("");
|
|
11086
11375
|
return `<section class="contract ${contract.pass ? "pass" : "fail"}">
|
|
11087
11376
|
<div class="contract-header">
|
|
11088
11377
|
<div>
|
|
11089
|
-
<p class="eyebrow">${
|
|
11090
|
-
<h2>${
|
|
11378
|
+
<p class="eyebrow">${escapeHtml20(contract.toolName)}</p>
|
|
11379
|
+
<h2>${escapeHtml20(contract.label ?? contract.contractId)}</h2>
|
|
11091
11380
|
</div>
|
|
11092
11381
|
<strong class="${contract.pass ? "pass" : "fail"}">${contract.pass ? "Passing" : "Failing"}</strong>
|
|
11093
11382
|
</div>
|
|
@@ -11097,7 +11386,7 @@ var renderVoiceToolContractHTML = (report, options = {}) => {
|
|
|
11097
11386
|
</table>
|
|
11098
11387
|
</section>`;
|
|
11099
11388
|
}).join("");
|
|
11100
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11389
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml20(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.contract{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.14),rgba(245,158,11,.12))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.contract-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}h2{margin:.2rem 0 1rem}.pass{color:#86efac}.fail{color:#fca5a5}.contract.fail{border-color:rgba(248,113,113,.45)}table{border-collapse:collapse;width:100%}td,th{border-bottom:1px solid #2a323a;padding:12px;text-align:left;vertical-align:top}th{color:#a8b0b8;font-size:.82rem}@media(max-width:800px){main{padding:18px}table{display:block;overflow:auto}.contract-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Tool Reliability</p><h1>${escapeHtml20(title)}</h1><div class="summary"><span class="pill ${report.status === "pass" ? "pass" : "fail"}">${escapeHtml20(report.status)}</span><span class="pill">${String(report.passed)} passing</span><span class="pill">${String(report.failed)} failing</span><span class="pill">${String(report.total)} contracts</span></div></section>${contracts || '<section class="contract"><p>No tool contracts configured.</p></section>'}</main></body></html>`;
|
|
11101
11390
|
};
|
|
11102
11391
|
var createVoiceToolContractJSONHandler = (options) => () => runVoiceToolContractSuite(options);
|
|
11103
11392
|
var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
@@ -11114,7 +11403,7 @@ var createVoiceToolContractHTMLHandler = (options) => async () => {
|
|
|
11114
11403
|
var createVoiceToolContractRoutes = (options) => {
|
|
11115
11404
|
const path = options.path ?? "/api/tool-contracts";
|
|
11116
11405
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11117
|
-
const routes = new
|
|
11406
|
+
const routes = new Elysia19({
|
|
11118
11407
|
name: options.name ?? "absolutejs-voice-tool-contracts"
|
|
11119
11408
|
}).get(path, createVoiceToolContractJSONHandler(options));
|
|
11120
11409
|
if (htmlPath) {
|
|
@@ -11124,7 +11413,7 @@ var createVoiceToolContractRoutes = (options) => {
|
|
|
11124
11413
|
};
|
|
11125
11414
|
|
|
11126
11415
|
// src/simulationSuite.ts
|
|
11127
|
-
var
|
|
11416
|
+
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11128
11417
|
var summarizeSection = (report) => ({
|
|
11129
11418
|
failed: report.failed,
|
|
11130
11419
|
passed: report.passed,
|
|
@@ -11260,20 +11549,20 @@ var renderSection = (label, summary) => {
|
|
|
11260
11549
|
if (!summary) {
|
|
11261
11550
|
return "";
|
|
11262
11551
|
}
|
|
11263
|
-
return `<article class="${
|
|
11552
|
+
return `<article class="${escapeHtml21(summary.status)}"><span>${escapeHtml21(label)}</span><strong>${escapeHtml21(summary.status)}</strong><p>${summary.passed}/${summary.total} passed, ${summary.failed} failed.</p></article>`;
|
|
11264
11553
|
};
|
|
11265
11554
|
var renderAction = (action) => {
|
|
11266
|
-
const content = `<strong>${
|
|
11267
|
-
return action.href ? `<a class="action" href="${
|
|
11555
|
+
const content = `<strong>${escapeHtml21(action.label)}</strong><p>${escapeHtml21(action.description)}</p><span>${escapeHtml21(action.section)} / ${escapeHtml21(action.severity)}</span>`;
|
|
11556
|
+
return action.href ? `<a class="action" href="${escapeHtml21(action.href)}">${content}</a>` : `<article class="action">${content}</article>`;
|
|
11268
11557
|
};
|
|
11269
11558
|
var renderVoiceSimulationSuiteHTML = (report, options = {}) => {
|
|
11270
11559
|
const title = options.title ?? "Voice Simulation Suite";
|
|
11271
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11560
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml21(title)}</title><style>body{background:#10151c;color:#f8f3e7;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1080px;padding:32px}.hero{background:linear-gradient(135deg,rgba(34,197,94,.18),rgba(59,130,246,.12));border:1px solid #283544;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#93c5fd;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.badge{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.fail{color:#fca5a5}.grid,.actions{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));margin:18px 0}.grid article,.action{background:#151d27;border:1px solid #283544;border-radius:18px;color:inherit;padding:16px;text-decoration:none}.grid span,.action span{color:#aab5c0}.grid strong{display:block;font-size:2rem;margin:.25rem 0;text-transform:uppercase}.action strong{display:block;color:#f8f3e7;margin-bottom:.35rem}.action p{color:#d8dee6;margin:.3rem 0 .6rem}pre{background:#151d27;border:1px solid #283544;border-radius:18px;overflow:auto;padding:16px}</style></head><body><main><section class="hero"><p class="eyebrow">Pre-production proof</p><h1>${escapeHtml21(title)}</h1><p>One report for session quality, scenario evals, fixture simulations, tool contracts, and outcome contracts.</p><p class="badge ${escapeHtml21(report.status)}">Status: ${escapeHtml21(report.status)}</p><section class="grid">${renderSection("Sessions", report.summary.sessions)}${renderSection("Scenarios", report.summary.scenarios)}${renderSection("Fixtures", report.summary.fixtures)}${renderSection("Tools", report.summary.tools)}${renderSection("Outcomes", report.summary.outcomes)}</section></section><h2>Actions</h2><section class="actions">${report.actions.length > 0 ? report.actions.map(renderAction).join("") : '<article class="action"><strong>No action required</strong><p>All enabled simulation sections are passing.</p></article>'}</section><pre>${escapeHtml21(JSON.stringify({ summary: report.summary, actions: report.actions }, null, 2))}</pre></main></body></html>`;
|
|
11272
11561
|
};
|
|
11273
11562
|
var createVoiceSimulationSuiteRoutes = (options) => {
|
|
11274
11563
|
const path = options.path ?? "/api/voice/simulations";
|
|
11275
11564
|
const htmlPath = options.htmlPath === undefined ? "/voice/simulations" : options.htmlPath;
|
|
11276
|
-
const app = new
|
|
11565
|
+
const app = new Elysia20({
|
|
11277
11566
|
name: options.name ?? "absolutejs-voice-simulation-suite"
|
|
11278
11567
|
}).get(path, () => runVoiceSimulationSuite(options));
|
|
11279
11568
|
if (htmlPath) {
|
|
@@ -11585,10 +11874,10 @@ var createVoiceWorkflowContractHandler = (input) => {
|
|
|
11585
11874
|
};
|
|
11586
11875
|
};
|
|
11587
11876
|
// src/turnLatency.ts
|
|
11588
|
-
import { Elysia as
|
|
11877
|
+
import { Elysia as Elysia21 } from "elysia";
|
|
11589
11878
|
var DEFAULT_WARN_AFTER_MS = 1800;
|
|
11590
11879
|
var DEFAULT_FAIL_AFTER_MS = 3200;
|
|
11591
|
-
var
|
|
11880
|
+
var escapeHtml22 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11592
11881
|
var firstNumber2 = (values) => values.filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11593
11882
|
var getString10 = (value) => typeof value === "string" && value.trim() ? value : undefined;
|
|
11594
11883
|
var createTraceStageIndex = (events) => {
|
|
@@ -11702,11 +11991,11 @@ var summarizeVoiceTurnLatency = async (options) => {
|
|
|
11702
11991
|
var formatMs2 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
11703
11992
|
var renderVoiceTurnLatencyHTML = (report, options = {}) => {
|
|
11704
11993
|
const title = options.title ?? "Voice Turn Latency";
|
|
11705
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
11706
|
-
<header><div><p class="eyebrow">${
|
|
11707
|
-
<dl>${turn.stages.map((stage) => `<div><dt>${
|
|
11994
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml22(turn.status)}">
|
|
11995
|
+
<header><div><p class="eyebrow">${escapeHtml22(turn.sessionId)} \xB7 ${escapeHtml22(turn.turnId)}</p><h2>${escapeHtml22(turn.text || "Empty turn")}</h2></div><strong>${escapeHtml22(turn.status)}</strong></header>
|
|
11996
|
+
<dl>${turn.stages.map((stage) => `<div><dt>${escapeHtml22(stage.label)}</dt><dd>${escapeHtml22(formatMs2(stage.valueMs))}</dd></div>`).join("")}</dl>
|
|
11708
11997
|
</article>`).join("");
|
|
11709
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
11998
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml22(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(251,191,36,.1))}.eyebrow{color:#5eead4;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.empty{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{font-weight:900;margin:0}@media(max-width:800px){main{padding:18px}.turn header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">End-to-end responsiveness</p><h1>${escapeHtml22(title)}</h1><div class="summary"><span class="pill ${escapeHtml22(report.status)}">${escapeHtml22(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">avg ${escapeHtml22(formatMs2(report.averageTotalMs))}</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
11710
11999
|
};
|
|
11711
12000
|
var createVoiceTurnLatencyJSONHandler = (options) => async () => summarizeVoiceTurnLatency(options);
|
|
11712
12001
|
var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
@@ -11723,7 +12012,7 @@ var createVoiceTurnLatencyHTMLHandler = (options) => async () => {
|
|
|
11723
12012
|
var createVoiceTurnLatencyRoutes = (options) => {
|
|
11724
12013
|
const path = options.path ?? "/api/turn-latency";
|
|
11725
12014
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11726
|
-
const routes = new
|
|
12015
|
+
const routes = new Elysia21({
|
|
11727
12016
|
name: options.name ?? "absolutejs-voice-turn-latency"
|
|
11728
12017
|
}).get(path, createVoiceTurnLatencyJSONHandler(options));
|
|
11729
12018
|
if (htmlPath) {
|
|
@@ -11732,8 +12021,8 @@ var createVoiceTurnLatencyRoutes = (options) => {
|
|
|
11732
12021
|
return routes;
|
|
11733
12022
|
};
|
|
11734
12023
|
// src/liveLatency.ts
|
|
11735
|
-
import { Elysia as
|
|
11736
|
-
var
|
|
12024
|
+
import { Elysia as Elysia22 } from "elysia";
|
|
12025
|
+
var escapeHtml23 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11737
12026
|
var percentile = (values, percentileValue) => {
|
|
11738
12027
|
if (values.length === 0) {
|
|
11739
12028
|
return;
|
|
@@ -11781,13 +12070,13 @@ var summarizeVoiceLiveLatency = async (options) => {
|
|
|
11781
12070
|
var formatMs3 = (value) => typeof value === "number" ? `${Math.round(value)}ms` : "n/a";
|
|
11782
12071
|
var renderVoiceLiveLatencyHTML = (report, options = {}) => {
|
|
11783
12072
|
const title = options.title ?? "Voice Live Latency";
|
|
11784
|
-
const rows = report.recent.map((sample) => `<tr><td>${
|
|
11785
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
12073
|
+
const rows = report.recent.map((sample) => `<tr><td>${escapeHtml23(sample.sessionId)}</td><td>${escapeHtml23(formatMs3(sample.latencyMs))}</td><td>${escapeHtml23(sample.status ?? "unknown")}</td><td>${escapeHtml23(new Date(sample.at).toLocaleString())}</td></tr>`).join("");
|
|
12074
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml23(title)}</title><style>body{background:#0c0f14;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1060px;padding:32px}.hero{background:linear-gradient(135deg,rgba(94,234,212,.16),rgba(245,158,11,.1));border:1px solid #26313d;border-radius:28px;margin-bottom:18px;padding:28px}.eyebrow{color:#5eead4;font-weight:900;letter-spacing:.12em;text-transform:uppercase}h1{font-size:clamp(2.4rem,6vw,5rem);line-height:.9;margin:.2rem 0 1rem}.status{border:1px solid #3f3f46;border-radius:999px;display:inline-flex;padding:8px 12px}.pass{color:#86efac}.warn,.empty{color:#fbbf24}.fail{color:#fca5a5}.metrics{display:grid;gap:14px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));margin:18px 0}.metrics article,table{background:#141922;border:1px solid #26313d;border-radius:18px}.metrics article{padding:16px}.metrics span{color:#a8b0b8}.metrics strong{display:block;font-size:2rem;margin-top:.25rem}table{border-collapse:collapse;overflow:hidden;width:100%}td,th{border-bottom:1px solid #26313d;padding:12px;text-align:left}@media(max-width:760px){main{padding:20px}}</style></head><body><main><section class="hero"><p class="eyebrow">Browser proof</p><h1>${escapeHtml23(title)}</h1><p>Recent real browser speech-to-assistant response measurements from persisted <code>client.live_latency</code> traces.</p><p class="status ${escapeHtml23(report.status)}">Status: ${escapeHtml23(report.status)}</p><section class="metrics"><article><span>p50</span><strong>${escapeHtml23(formatMs3(report.p50LatencyMs))}</strong></article><article><span>p95</span><strong>${escapeHtml23(formatMs3(report.p95LatencyMs))}</strong></article><article><span>Average</span><strong>${escapeHtml23(formatMs3(report.averageLatencyMs))}</strong></article><article><span>Samples</span><strong>${String(report.total)}</strong></article></section></section><table><thead><tr><th>Session</th><th>Latency</th><th>Status</th><th>Measured</th></tr></thead><tbody>${rows || '<tr><td colspan="4">No live latency samples yet.</td></tr>'}</tbody></table></main></body></html>`;
|
|
11786
12075
|
};
|
|
11787
12076
|
var createVoiceLiveLatencyRoutes = (options) => {
|
|
11788
12077
|
const path = options.path ?? "/api/live-latency";
|
|
11789
12078
|
const htmlPath = options.htmlPath === undefined ? "/live-latency" : options.htmlPath;
|
|
11790
|
-
const routes = new
|
|
12079
|
+
const routes = new Elysia22({
|
|
11791
12080
|
name: options.name ?? "absolutejs-voice-live-latency"
|
|
11792
12081
|
}).get(path, () => summarizeVoiceLiveLatency(options));
|
|
11793
12082
|
if (htmlPath) {
|
|
@@ -11804,9 +12093,9 @@ var createVoiceLiveLatencyRoutes = (options) => {
|
|
|
11804
12093
|
return routes;
|
|
11805
12094
|
};
|
|
11806
12095
|
// src/turnQuality.ts
|
|
11807
|
-
import { Elysia as
|
|
12096
|
+
import { Elysia as Elysia23 } from "elysia";
|
|
11808
12097
|
var DEFAULT_CONFIDENCE_WARN_THRESHOLD = 0.72;
|
|
11809
|
-
var
|
|
12098
|
+
var escapeHtml24 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11810
12099
|
var getTurnLatencyMs = (turn) => {
|
|
11811
12100
|
const firstTranscriptAt = turn.transcripts.map((transcript) => transcript.endedAtMs ?? transcript.startedAtMs).filter((value) => typeof value === "number").sort((left, right) => left - right)[0];
|
|
11812
12101
|
if (firstTranscriptAt === undefined) {
|
|
@@ -11877,24 +12166,24 @@ var summarizeVoiceTurnQuality = async (options) => {
|
|
|
11877
12166
|
};
|
|
11878
12167
|
var renderVoiceTurnQualityHTML = (report, options = {}) => {
|
|
11879
12168
|
const title = options.title ?? "Voice Turn Quality";
|
|
11880
|
-
const turns = report.turns.map((turn) => `<article class="turn ${
|
|
12169
|
+
const turns = report.turns.map((turn) => `<article class="turn ${escapeHtml24(turn.status)}">
|
|
11881
12170
|
<div class="turn-header">
|
|
11882
12171
|
<div>
|
|
11883
|
-
<p class="eyebrow">${
|
|
11884
|
-
<h2>${
|
|
12172
|
+
<p class="eyebrow">${escapeHtml24(turn.sessionId)} \xB7 ${escapeHtml24(turn.turnId)}</p>
|
|
12173
|
+
<h2>${escapeHtml24(turn.text || "Empty turn")}</h2>
|
|
11885
12174
|
</div>
|
|
11886
|
-
<strong>${
|
|
12175
|
+
<strong>${escapeHtml24(turn.status)}</strong>
|
|
11887
12176
|
</div>
|
|
11888
12177
|
<dl>
|
|
11889
|
-
<div><dt>Source</dt><dd>${
|
|
12178
|
+
<div><dt>Source</dt><dd>${escapeHtml24(turn.source ?? "unknown")}</dd></div>
|
|
11890
12179
|
<div><dt>Confidence</dt><dd>${turn.averageConfidence === undefined ? "n/a" : `${Math.round(turn.averageConfidence * 100)}%`}</dd></div>
|
|
11891
|
-
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${
|
|
11892
|
-
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${
|
|
12180
|
+
<div><dt>Fallback</dt><dd>${turn.fallbackUsed ? `yes (${escapeHtml24(turn.fallbackSelectionReason ?? "selected")})` : "no"}</dd></div>
|
|
12181
|
+
<div><dt>Correction</dt><dd>${turn.correctionChanged ? `changed${turn.correctionProvider ? ` by ${escapeHtml24(turn.correctionProvider)}` : ""}` : "none"}</dd></div>
|
|
11893
12182
|
<div><dt>Transcripts</dt><dd>${String(turn.selectedTranscriptCount)} selected \xB7 ${String(turn.finalTranscriptCount)} final \xB7 ${String(turn.partialTranscriptCount)} partial</dd></div>
|
|
11894
12183
|
<div><dt>Cost</dt><dd>${turn.costUnits === undefined ? "n/a" : String(turn.costUnits)}</dd></div>
|
|
11895
12184
|
</dl>
|
|
11896
12185
|
</article>`).join("");
|
|
11897
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${
|
|
12186
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>${escapeHtml24(title)}</title><style>body{background:#101316;color:#f6f2e8;font-family:ui-sans-serif,system-ui,sans-serif;margin:0}main{margin:auto;max-width:1180px;padding:32px}.hero,.turn{background:#181d22;border:1px solid #2a323a;border-radius:20px;margin-bottom:16px;padding:20px}.hero{background:linear-gradient(135deg,rgba(251,191,36,.16),rgba(34,197,94,.1))}.eyebrow{color:#fbbf24;font-size:.78rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase}h1{font-size:clamp(2.3rem,6vw,5rem);letter-spacing:-.06em;line-height:.9;margin:.2rem 0 1rem}h2{margin:.2rem 0 1rem}.summary{display:flex;flex-wrap:wrap;gap:10px}.pill{background:#0f1217;border:1px solid #3f3f46;border-radius:999px;padding:7px 10px}.turn-header{align-items:flex-start;display:flex;gap:16px;justify-content:space-between}.pass{color:#86efac}.warn,.unknown{color:#fde68a}.fail{color:#fca5a5}.turn.fail{border-color:rgba(248,113,113,.45)}dl{display:grid;gap:8px;grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}dt{color:#a8b0b8;font-size:.8rem}dd{margin:0}@media(max-width:800px){main{padding:18px}.turn-header{display:block}}</style></head><body><main><section class="hero"><p class="eyebrow">Realtime STT Debugging</p><h1>${escapeHtml24(title)}</h1><div class="summary"><span class="pill ${escapeHtml24(report.status)}">${escapeHtml24(report.status)}</span><span class="pill">${String(report.total)} turns</span><span class="pill">${String(report.warnings)} warnings</span><span class="pill">${String(report.failed)} failed</span><span class="pill">${String(report.sessions)} sessions</span></div></section>${turns || '<section class="turn"><p>No committed turns found.</p></section>'}</main></body></html>`;
|
|
11898
12187
|
};
|
|
11899
12188
|
var createVoiceTurnQualityJSONHandler = (options) => async () => summarizeVoiceTurnQuality(options);
|
|
11900
12189
|
var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
@@ -11911,7 +12200,7 @@ var createVoiceTurnQualityHTMLHandler = (options) => async () => {
|
|
|
11911
12200
|
var createVoiceTurnQualityRoutes = (options) => {
|
|
11912
12201
|
const path = options.path ?? "/api/turn-quality";
|
|
11913
12202
|
const htmlPath = options.htmlPath === undefined ? `${path}/htmx` : options.htmlPath;
|
|
11914
|
-
const routes = new
|
|
12203
|
+
const routes = new Elysia23({
|
|
11915
12204
|
name: options.name ?? "absolutejs-voice-turn-quality"
|
|
11916
12205
|
}).get(path, createVoiceTurnQualityJSONHandler(options));
|
|
11917
12206
|
if (htmlPath) {
|
|
@@ -11920,7 +12209,7 @@ var createVoiceTurnQualityRoutes = (options) => {
|
|
|
11920
12209
|
return routes;
|
|
11921
12210
|
};
|
|
11922
12211
|
// src/telephonyOutcome.ts
|
|
11923
|
-
import { Elysia as
|
|
12212
|
+
import { Elysia as Elysia24 } from "elysia";
|
|
11924
12213
|
var DEFAULT_COMPLETED_STATUSES = [
|
|
11925
12214
|
"answered",
|
|
11926
12215
|
"completed",
|
|
@@ -12567,7 +12856,7 @@ var createVoiceTelephonyWebhookHandler = (options = {}) => async (input) => {
|
|
|
12567
12856
|
var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
12568
12857
|
const path = options.path ?? "/api/voice/telephony/webhook";
|
|
12569
12858
|
const handler = createVoiceTelephonyWebhookHandler(options);
|
|
12570
|
-
return new
|
|
12859
|
+
return new Elysia24({
|
|
12571
12860
|
name: options.name ?? "absolutejs-voice-telephony-webhooks"
|
|
12572
12861
|
}).post(path, async ({ query, request }) => {
|
|
12573
12862
|
try {
|
|
@@ -12588,15 +12877,15 @@ var createVoiceTelephonyWebhookRoutes = (options = {}) => {
|
|
|
12588
12877
|
});
|
|
12589
12878
|
};
|
|
12590
12879
|
// src/phoneAgent.ts
|
|
12591
|
-
import { Elysia as
|
|
12880
|
+
import { Elysia as Elysia28 } from "elysia";
|
|
12592
12881
|
|
|
12593
12882
|
// src/telephony/plivo.ts
|
|
12594
12883
|
import { Buffer as Buffer4 } from "buffer";
|
|
12595
|
-
import { Elysia as
|
|
12884
|
+
import { Elysia as Elysia26 } from "elysia";
|
|
12596
12885
|
|
|
12597
12886
|
// src/telephony/twilio.ts
|
|
12598
12887
|
import { Buffer as Buffer3 } from "buffer";
|
|
12599
|
-
import { Elysia as
|
|
12888
|
+
import { Elysia as Elysia25 } from "elysia";
|
|
12600
12889
|
var TWILIO_MULAW_SAMPLE_RATE = 8000;
|
|
12601
12890
|
var VOICE_PCM_SAMPLE_RATE = 16000;
|
|
12602
12891
|
var escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
@@ -12626,7 +12915,7 @@ var resolveTwilioStreamParameters = async (parameters, input) => {
|
|
|
12626
12915
|
return parameters;
|
|
12627
12916
|
};
|
|
12628
12917
|
var joinUrlPath = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
12629
|
-
var
|
|
12918
|
+
var escapeHtml25 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
12630
12919
|
var getWebhookVerificationUrl = (webhook, input) => {
|
|
12631
12920
|
if (!webhook?.verificationUrl) {
|
|
12632
12921
|
return;
|
|
@@ -12669,23 +12958,23 @@ var buildTwilioVoiceSetupStatus = async (options, input) => {
|
|
|
12669
12958
|
};
|
|
12670
12959
|
var renderTwilioVoiceSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
12671
12960
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio setup</p>
|
|
12672
|
-
<h1>${
|
|
12961
|
+
<h1>${escapeHtml25(title)}</h1>
|
|
12673
12962
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
12674
12963
|
<section>
|
|
12675
12964
|
<h2>URLs</h2>
|
|
12676
12965
|
<ul>
|
|
12677
|
-
<li><strong>TwiML:</strong> <code>${
|
|
12678
|
-
<li><strong>Media stream:</strong> <code>${
|
|
12679
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
12966
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml25(status.urls.twiml)}</code></li>
|
|
12967
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml25(status.urls.stream)}</code></li>
|
|
12968
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml25(status.urls.webhook)}</code></li>
|
|
12680
12969
|
</ul>
|
|
12681
12970
|
</section>
|
|
12682
12971
|
<section>
|
|
12683
12972
|
<h2>Signing</h2>
|
|
12684
12973
|
<p>Mode: <code>${status.signing.mode}</code></p>
|
|
12685
|
-
${status.signing.verificationUrl ? `<p>Verification URL: <code>${
|
|
12974
|
+
${status.signing.verificationUrl ? `<p>Verification URL: <code>${escapeHtml25(status.signing.verificationUrl)}</code></p>` : ""}
|
|
12686
12975
|
</section>
|
|
12687
|
-
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
12688
|
-
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
12976
|
+
${status.missing.length ? `<section><h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml25(name)}</code></li>`).join("")}</ul></section>` : ""}
|
|
12977
|
+
${status.warnings.length ? `<section><h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml25(warning)}</li>`).join("")}</ul></section>` : ""}
|
|
12689
12978
|
</main>`;
|
|
12690
12979
|
var extractTwilioStreamUrl = (twiml) => twiml.match(/<Stream\b[^>]*\surl="([^"]+)"/i)?.[1]?.replaceAll("&", "&");
|
|
12691
12980
|
var createSmokeCheck = (name, status, message, details) => ({
|
|
@@ -12696,20 +12985,20 @@ var createSmokeCheck = (name, status, message, details) => ({
|
|
|
12696
12985
|
});
|
|
12697
12986
|
var renderTwilioVoiceSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
12698
12987
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Twilio smoke test</p>
|
|
12699
|
-
<h1>${
|
|
12988
|
+
<h1>${escapeHtml25(title)}</h1>
|
|
12700
12989
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
12701
12990
|
<section>
|
|
12702
12991
|
<h2>Checks</h2>
|
|
12703
12992
|
<ul>
|
|
12704
|
-
${report.checks.map((check) => `<li><strong>${
|
|
12993
|
+
${report.checks.map((check) => `<li><strong>${escapeHtml25(check.name)}</strong>: ${escapeHtml25(check.status)}${check.message ? ` - ${escapeHtml25(check.message)}` : ""}</li>`).join("")}
|
|
12705
12994
|
</ul>
|
|
12706
12995
|
</section>
|
|
12707
12996
|
<section>
|
|
12708
12997
|
<h2>Observed URLs</h2>
|
|
12709
12998
|
<ul>
|
|
12710
|
-
<li><strong>TwiML:</strong> <code>${
|
|
12711
|
-
<li><strong>Stream:</strong> <code>${
|
|
12712
|
-
<li><strong>Webhook:</strong> <code>${
|
|
12999
|
+
<li><strong>TwiML:</strong> <code>${escapeHtml25(report.setup.urls.twiml)}</code></li>
|
|
13000
|
+
<li><strong>Stream:</strong> <code>${escapeHtml25(report.twiml?.streamUrl ?? report.setup.urls.stream)}</code></li>
|
|
13001
|
+
<li><strong>Webhook:</strong> <code>${escapeHtml25(report.setup.urls.webhook)}</code></li>
|
|
12713
13002
|
</ul>
|
|
12714
13003
|
</section>
|
|
12715
13004
|
</main>`;
|
|
@@ -13169,7 +13458,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
13169
13458
|
const smokePath = options.smoke?.path === false ? false : options.smoke?.path ?? "/api/voice/twilio/smoke";
|
|
13170
13459
|
const bridges = new WeakMap;
|
|
13171
13460
|
const webhookPolicy = options.webhook?.policy ?? options.outcomePolicy ?? createVoiceTelephonyOutcomePolicy();
|
|
13172
|
-
const app = new
|
|
13461
|
+
const app = new Elysia25({
|
|
13173
13462
|
name: options.name ?? "absolutejs-voice-twilio"
|
|
13174
13463
|
}).get(twimlPath, async ({ query, request }) => {
|
|
13175
13464
|
const streamUrl = await resolveTwilioStreamUrl(options, {
|
|
@@ -13306,7 +13595,7 @@ var createTwilioVoiceRoutes = (options) => {
|
|
|
13306
13595
|
|
|
13307
13596
|
// src/telephony/plivo.ts
|
|
13308
13597
|
var escapeXml3 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
13309
|
-
var
|
|
13598
|
+
var escapeHtml26 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
13310
13599
|
var joinUrlPath2 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
13311
13600
|
var resolveRequestOrigin2 = (request) => {
|
|
13312
13601
|
const url = new URL(request.url);
|
|
@@ -13557,21 +13846,21 @@ var buildPlivoVoiceSetupStatus = async (options, input) => {
|
|
|
13557
13846
|
};
|
|
13558
13847
|
var renderPlivoSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13559
13848
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo setup</p>
|
|
13560
|
-
<h1>${
|
|
13849
|
+
<h1>${escapeHtml26(title)}</h1>
|
|
13561
13850
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
13562
13851
|
<ul>
|
|
13563
|
-
<li><strong>Answer XML:</strong> <code>${
|
|
13564
|
-
<li><strong>Audio stream:</strong> <code>${
|
|
13565
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
13852
|
+
<li><strong>Answer XML:</strong> <code>${escapeHtml26(status.urls.answer)}</code></li>
|
|
13853
|
+
<li><strong>Audio stream:</strong> <code>${escapeHtml26(status.urls.stream)}</code></li>
|
|
13854
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml26(status.urls.webhook)}</code></li>
|
|
13566
13855
|
</ul>
|
|
13567
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
13568
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
13856
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml26(name)}</code></li>`).join("")}</ul>` : ""}
|
|
13857
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml26(warning)}</li>`).join("")}</ul>` : ""}
|
|
13569
13858
|
</main>`;
|
|
13570
13859
|
var renderPlivoSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13571
13860
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Plivo smoke test</p>
|
|
13572
|
-
<h1>${
|
|
13861
|
+
<h1>${escapeHtml26(title)}</h1>
|
|
13573
13862
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
13574
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
13863
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml26(check.name)}</strong>: ${escapeHtml26(check.status)}${check.message ? ` - ${escapeHtml26(check.message)}` : ""}</li>`).join("")}</ul>
|
|
13575
13864
|
</main>`;
|
|
13576
13865
|
var runPlivoSmokeTest = async (input) => {
|
|
13577
13866
|
const setup = await buildPlivoVoiceSetupStatus(input.options, input);
|
|
@@ -13666,7 +13955,7 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
13666
13955
|
request: input.request
|
|
13667
13956
|
}) : verificationUrl ?? input.request.url
|
|
13668
13957
|
}) : undefined);
|
|
13669
|
-
const app = new
|
|
13958
|
+
const app = new Elysia26({
|
|
13670
13959
|
name: options.name ?? "absolutejs-voice-plivo"
|
|
13671
13960
|
}).get(answerPath, async ({ query, request }) => {
|
|
13672
13961
|
const streamUrl = await resolvePlivoStreamUrl(options, {
|
|
@@ -13777,9 +14066,9 @@ var createPlivoVoiceRoutes = (options = {}) => {
|
|
|
13777
14066
|
|
|
13778
14067
|
// src/telephony/telnyx.ts
|
|
13779
14068
|
import { Buffer as Buffer5 } from "buffer";
|
|
13780
|
-
import { Elysia as
|
|
14069
|
+
import { Elysia as Elysia27 } from "elysia";
|
|
13781
14070
|
var escapeXml4 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
13782
|
-
var
|
|
14071
|
+
var escapeHtml27 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
13783
14072
|
var joinUrlPath3 = (origin, path) => `${origin.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
|
|
13784
14073
|
var resolveRequestOrigin3 = (request) => {
|
|
13785
14074
|
const url = new URL(request.url);
|
|
@@ -13980,21 +14269,21 @@ var buildTelnyxVoiceSetupStatus = async (options, input) => {
|
|
|
13980
14269
|
};
|
|
13981
14270
|
var renderTelnyxSetupHTML = (status, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13982
14271
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx setup</p>
|
|
13983
|
-
<h1>${
|
|
14272
|
+
<h1>${escapeHtml27(title)}</h1>
|
|
13984
14273
|
<p><strong>Status:</strong> ${status.ready ? "Ready" : "Needs attention"}</p>
|
|
13985
14274
|
<ul>
|
|
13986
|
-
<li><strong>TeXML:</strong> <code>${
|
|
13987
|
-
<li><strong>Media stream:</strong> <code>${
|
|
13988
|
-
<li><strong>Status webhook:</strong> <code>${
|
|
14275
|
+
<li><strong>TeXML:</strong> <code>${escapeHtml27(status.urls.texml)}</code></li>
|
|
14276
|
+
<li><strong>Media stream:</strong> <code>${escapeHtml27(status.urls.stream)}</code></li>
|
|
14277
|
+
<li><strong>Status webhook:</strong> <code>${escapeHtml27(status.urls.webhook)}</code></li>
|
|
13989
14278
|
</ul>
|
|
13990
|
-
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${
|
|
13991
|
-
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${
|
|
14279
|
+
${status.missing.length ? `<h2>Missing env</h2><ul>${status.missing.map((name) => `<li><code>${escapeHtml27(name)}</code></li>`).join("")}</ul>` : ""}
|
|
14280
|
+
${status.warnings.length ? `<h2>Warnings</h2><ul>${status.warnings.map((warning) => `<li>${escapeHtml27(warning)}</li>`).join("")}</ul>` : ""}
|
|
13992
14281
|
</main>`;
|
|
13993
14282
|
var renderTelnyxSmokeHTML = (report, title) => `<main style="font-family: ui-sans-serif, system-ui; max-width: 860px; margin: 40px auto; padding: 0 20px;">
|
|
13994
14283
|
<p style="letter-spacing: .12em; text-transform: uppercase; color: #52606d;">Telnyx smoke test</p>
|
|
13995
|
-
<h1>${
|
|
14284
|
+
<h1>${escapeHtml27(title)}</h1>
|
|
13996
14285
|
<p><strong>Status:</strong> ${report.pass ? "Pass" : "Fail"}</p>
|
|
13997
|
-
<ul>${report.checks.map((check) => `<li><strong>${
|
|
14286
|
+
<ul>${report.checks.map((check) => `<li><strong>${escapeHtml27(check.name)}</strong>: ${escapeHtml27(check.status)}${check.message ? ` - ${escapeHtml27(check.message)}` : ""}</li>`).join("")}</ul>
|
|
13998
14287
|
</main>`;
|
|
13999
14288
|
var runTelnyxSmokeTest = async (input) => {
|
|
14000
14289
|
const setup = await buildTelnyxVoiceSetupStatus(input.options, input);
|
|
@@ -14088,7 +14377,7 @@ var createTelnyxVoiceRoutes = (options = {}) => {
|
|
|
14088
14377
|
publicKey: options.webhook?.publicKey,
|
|
14089
14378
|
toleranceSeconds: options.webhook?.toleranceSeconds
|
|
14090
14379
|
}) : undefined);
|
|
14091
|
-
const app = new
|
|
14380
|
+
const app = new Elysia27({
|
|
14092
14381
|
name: options.name ?? "absolutejs-voice-telnyx"
|
|
14093
14382
|
}).get(texmlPath, async ({ query, request }) => {
|
|
14094
14383
|
const streamUrl = await resolveTelnyxStreamUrl(options, {
|
|
@@ -14258,7 +14547,7 @@ var createVoicePhoneAgent = (options) => {
|
|
|
14258
14547
|
setupPath: resolveSetupPath(carrier),
|
|
14259
14548
|
smokePath: resolveSmokePath(carrier)
|
|
14260
14549
|
}));
|
|
14261
|
-
const app = new
|
|
14550
|
+
const app = new Elysia28({
|
|
14262
14551
|
name: options.name ?? "absolutejs-voice-phone-agent"
|
|
14263
14552
|
});
|
|
14264
14553
|
for (const carrier of options.carriers) {
|
|
@@ -16539,7 +16828,7 @@ var createVoiceMemoryStore = () => {
|
|
|
16539
16828
|
return { get, getOrCreate, list, remove, set };
|
|
16540
16829
|
};
|
|
16541
16830
|
// src/opsWebhook.ts
|
|
16542
|
-
import { Elysia as
|
|
16831
|
+
import { Elysia as Elysia29 } from "elysia";
|
|
16543
16832
|
var toHex5 = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
16544
16833
|
var signVoiceOpsWebhookBody = async (input) => {
|
|
16545
16834
|
const encoder = new TextEncoder;
|
|
@@ -16669,7 +16958,7 @@ var verifyVoiceOpsWebhookSignature = async (input) => {
|
|
|
16669
16958
|
};
|
|
16670
16959
|
var createVoiceOpsWebhookReceiverRoutes = (options = {}) => {
|
|
16671
16960
|
const path = options.path ?? "/api/voice-ops/webhook";
|
|
16672
|
-
return new
|
|
16961
|
+
return new Elysia29().post(path, async ({ body, request, set }) => {
|
|
16673
16962
|
const bodyText = typeof body === "string" ? body : JSON.stringify(body);
|
|
16674
16963
|
if (options.signingSecret) {
|
|
16675
16964
|
const verification = await verifyVoiceOpsWebhookSignature({
|
|
@@ -18478,6 +18767,7 @@ export {
|
|
|
18478
18767
|
summarizeVoiceIntegrationEvents,
|
|
18479
18768
|
summarizeVoiceHandoffHealth,
|
|
18480
18769
|
summarizeVoiceHandoffDeliveries,
|
|
18770
|
+
summarizeVoiceCampaigns,
|
|
18481
18771
|
summarizeVoiceBargeIn,
|
|
18482
18772
|
summarizeVoiceAssistantRuns,
|
|
18483
18773
|
summarizeVoiceAssistantHealth,
|
|
@@ -18533,6 +18823,7 @@ export {
|
|
|
18533
18823
|
renderVoiceHandoffHealthHTML,
|
|
18534
18824
|
renderVoiceEvalHTML,
|
|
18535
18825
|
renderVoiceEvalBaselineHTML,
|
|
18826
|
+
renderVoiceCampaignsHTML,
|
|
18536
18827
|
renderVoiceCallReviewMarkdown,
|
|
18537
18828
|
renderVoiceCallReviewHTML,
|
|
18538
18829
|
renderVoiceBargeInHTML,
|
|
@@ -18668,6 +18959,7 @@ export {
|
|
|
18668
18959
|
createVoiceMemoryTraceEventStore,
|
|
18669
18960
|
createVoiceMemoryStore,
|
|
18670
18961
|
createVoiceMemoryHandoffDeliveryStore,
|
|
18962
|
+
createVoiceMemoryCampaignStore,
|
|
18671
18963
|
createVoiceMemoryAssistantMemoryStore,
|
|
18672
18964
|
createVoiceLiveLatencyRoutes,
|
|
18673
18965
|
createVoiceLinearIssueUpdateSink,
|
|
@@ -18703,6 +18995,8 @@ export {
|
|
|
18703
18995
|
createVoiceExperiment,
|
|
18704
18996
|
createVoiceEvalRoutes,
|
|
18705
18997
|
createVoiceDiagnosticsRoutes,
|
|
18998
|
+
createVoiceCampaignRoutes,
|
|
18999
|
+
createVoiceCampaign,
|
|
18706
19000
|
createVoiceCallReviewRecorder,
|
|
18707
19001
|
createVoiceCallReviewFromSession,
|
|
18708
19002
|
createVoiceCallReviewFromLiveTelephonyReport,
|