@voicelayer/sdk 0.5.0 → 0.5.1
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/brain/index.d.ts +2 -2
- package/dist/brain/index.js +52 -0
- package/dist/index.d.ts +314 -297
- package/dist/index.js +401 -195
- package/dist/runtime/text-session.d.ts +1 -1
- package/dist/runtime/text-session.js +155 -3
- package/dist/{text-session-Bf4Jni7H.d.ts → text-session-BbG3VG50.d.ts} +153 -122
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -10,8 +10,8 @@ import { Readable } from 'stream';
|
|
|
10
10
|
import { lookup } from 'dns/promises';
|
|
11
11
|
import { voice as voice$1, llm } from '@livekit/agents';
|
|
12
12
|
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten';
|
|
13
|
+
import { randomUUID, createHash } from 'crypto';
|
|
13
14
|
import { setup, createActor } from 'xstate';
|
|
14
|
-
import { randomUUID } from 'crypto';
|
|
15
15
|
import os from 'os';
|
|
16
16
|
import { Resend } from 'resend';
|
|
17
17
|
import { logs } from '@opentelemetry/api-logs';
|
|
@@ -303,6 +303,8 @@ var init_flow = __esm({
|
|
|
303
303
|
"ask",
|
|
304
304
|
"confirm",
|
|
305
305
|
"tool",
|
|
306
|
+
// Send an email through the workspace's email (metered, wallet-gated; From on a verified domain) — burn-down G-22.
|
|
307
|
+
"email",
|
|
306
308
|
"decision",
|
|
307
309
|
"handoff",
|
|
308
310
|
"end",
|
|
@@ -1369,6 +1371,61 @@ var init_forms = __esm({
|
|
|
1369
1371
|
"../contracts/src/forms.ts"() {
|
|
1370
1372
|
}
|
|
1371
1373
|
});
|
|
1374
|
+
function isEmailAddress(value) {
|
|
1375
|
+
return EMAIL_RE.test(value) && value.length <= 254;
|
|
1376
|
+
}
|
|
1377
|
+
function normalizeDomain(input) {
|
|
1378
|
+
return input.trim().toLowerCase().replace(/^[a-z]+:\/\//, "").replace(/\/.*$/, "").replace(/\.$/, "");
|
|
1379
|
+
}
|
|
1380
|
+
function isDomainName(value) {
|
|
1381
|
+
return DOMAIN_RE.test(value);
|
|
1382
|
+
}
|
|
1383
|
+
var EMAIL_RE, EMAIL_SUBJECT_MAX, EMAIL_TEXT_MAX, EMAIL_HTML_MAX, address, DOMAIN_RE, EmailDomainStatus, EmailDnsRecord;
|
|
1384
|
+
var init_email = __esm({
|
|
1385
|
+
"../contracts/src/email.ts"() {
|
|
1386
|
+
EMAIL_RE = /^[^\s@<>"]+@[^\s@<>"]+\.[^\s@<>"]+$/;
|
|
1387
|
+
EMAIL_SUBJECT_MAX = 200;
|
|
1388
|
+
EMAIL_TEXT_MAX = 5e4;
|
|
1389
|
+
EMAIL_HTML_MAX = 2e5;
|
|
1390
|
+
address = z.string().max(254).refine(isEmailAddress, { message: "must be an email address" });
|
|
1391
|
+
z.object({
|
|
1392
|
+
to: address,
|
|
1393
|
+
subject: z.string().min(1).max(EMAIL_SUBJECT_MAX),
|
|
1394
|
+
text: z.string().min(1).max(EMAIL_TEXT_MAX),
|
|
1395
|
+
html: z.string().min(1).max(EMAIL_HTML_MAX).optional(),
|
|
1396
|
+
// An address on one of the workspace's verified sending domains; omitted → the platform sender.
|
|
1397
|
+
from: address.optional(),
|
|
1398
|
+
// The display name shown beside `from` (or beside the platform sender's address).
|
|
1399
|
+
fromName: z.string().trim().min(1).max(64).refine((v) => !/[\r\n<>"]/.test(v), { message: 'must not contain line breaks, <, > or "' }).optional(),
|
|
1400
|
+
// Where a reply goes — any address; it needs no verification.
|
|
1401
|
+
replyTo: address.optional(),
|
|
1402
|
+
// The conversation (a call, a text conversation) the email belongs to — a conversation sends at most 3 emails.
|
|
1403
|
+
conversationId: z.string().min(1).max(128).optional()
|
|
1404
|
+
});
|
|
1405
|
+
DOMAIN_RE = /^(?=.{4,253}$)(?!-)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
|
|
1406
|
+
z.object({
|
|
1407
|
+
domain: z.string().max(253).transform(normalizeDomain).refine(isDomainName, { message: "must be a domain like example.com" })
|
|
1408
|
+
});
|
|
1409
|
+
EmailDomainStatus = z.enum(["pending", "verified", "failed"]);
|
|
1410
|
+
EmailDnsRecord = z.object({
|
|
1411
|
+
type: z.enum(["CNAME", "TXT"]),
|
|
1412
|
+
name: z.string(),
|
|
1413
|
+
value: z.string(),
|
|
1414
|
+
// dkim: proves the domain (required); dmarc: recommended, only when the domain has no DMARC record yet.
|
|
1415
|
+
purpose: z.enum(["dkim", "dmarc"]),
|
|
1416
|
+
required: z.boolean()
|
|
1417
|
+
});
|
|
1418
|
+
z.object({
|
|
1419
|
+
id: z.string(),
|
|
1420
|
+
domain: z.string(),
|
|
1421
|
+
status: EmailDomainStatus,
|
|
1422
|
+
records: z.array(EmailDnsRecord),
|
|
1423
|
+
createdAt: z.string().datetime(),
|
|
1424
|
+
lastCheckedAt: z.string().datetime().nullable(),
|
|
1425
|
+
verifiedAt: z.string().datetime().nullable()
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1372
1429
|
var AgentConfigVersionDTO, AgentConfigDiffField, AgentConfigChange;
|
|
1373
1430
|
var init_agent_versions = __esm({
|
|
1374
1431
|
"../contracts/src/agent-versions.ts"() {
|
|
@@ -2117,6 +2174,20 @@ function nodeConfigFor(node) {
|
|
|
2117
2174
|
})()
|
|
2118
2175
|
};
|
|
2119
2176
|
}
|
|
2177
|
+
case "email": {
|
|
2178
|
+
const text = (key) => typeof data[key] === "string" && data[key].trim() ? data[key] : void 0;
|
|
2179
|
+
const from = text("from");
|
|
2180
|
+
const fromName = text("fromName");
|
|
2181
|
+
const replyTo = text("replyTo");
|
|
2182
|
+
return {
|
|
2183
|
+
to: text("to") ?? "",
|
|
2184
|
+
subject: text("subject") ?? "",
|
|
2185
|
+
body: text("body") ?? "",
|
|
2186
|
+
...from ? { from: from.trim() } : {},
|
|
2187
|
+
...fromName ? { fromName: fromName.trim() } : {},
|
|
2188
|
+
...replyTo ? { replyTo: replyTo.trim() } : {}
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
2120
2191
|
case "gate":
|
|
2121
2192
|
return {
|
|
2122
2193
|
requiredFields: Array.isArray(data.requiredFields) ? data.requiredFields.filter((x) => typeof x === "string") : []
|
|
@@ -2470,6 +2541,7 @@ var init_flow_validate = __esm({
|
|
|
2470
2541
|
init_secret_headers();
|
|
2471
2542
|
init_network_address();
|
|
2472
2543
|
init_dispatch_metadata();
|
|
2544
|
+
init_email();
|
|
2473
2545
|
init_flow_edges();
|
|
2474
2546
|
FlowIssueSeverity = z.enum(["error", "warning"]);
|
|
2475
2547
|
FlowIssue = z.object({
|
|
@@ -4245,6 +4317,7 @@ var init_src = __esm({
|
|
|
4245
4317
|
init_memory();
|
|
4246
4318
|
init_participants();
|
|
4247
4319
|
init_forms();
|
|
4320
|
+
init_email();
|
|
4248
4321
|
init_consultation();
|
|
4249
4322
|
init_agent_config();
|
|
4250
4323
|
init_agent_versions();
|
|
@@ -6086,7 +6159,7 @@ function menuDigitOf(b) {
|
|
|
6086
6159
|
function menuLabelOf(label) {
|
|
6087
6160
|
return label.replace(/^\s*[0-9*#]\s*[-—–:.]*\s*/, "").trim() || label.trim();
|
|
6088
6161
|
}
|
|
6089
|
-
var ASK_MAX_CLARIFY, COLLECT_MAX_STALLED_TURNS, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, askExecutor, gateExecutor, endExecutor, handoffExecutor, OPENING_SILENCE_MS, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, passthroughExecutor, EXECUTORS;
|
|
6162
|
+
var ASK_MAX_CLARIFY, COLLECT_MAX_STALLED_TURNS, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, askExecutor, gateExecutor, endExecutor, handoffExecutor, OPENING_SILENCE_MS, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, EmailStepTimeout, EMAIL_SEND_TIMEOUT_MS, emailExecutor, passthroughExecutor, EXECUTORS;
|
|
6090
6163
|
var init_executors = __esm({
|
|
6091
6164
|
"src/runtime/graph/executors.ts"() {
|
|
6092
6165
|
init_log();
|
|
@@ -6900,6 +6973,64 @@ ${collected}`);
|
|
|
6900
6973
|
if (outcome.kind === "handoff") return { kind: "handoff", to: outcome.to };
|
|
6901
6974
|
return { kind: "advance" };
|
|
6902
6975
|
};
|
|
6976
|
+
EmailStepTimeout = class extends Error {
|
|
6977
|
+
};
|
|
6978
|
+
EMAIL_SEND_TIMEOUT_MS = 1e4;
|
|
6979
|
+
emailExecutor = async (node, state, deps) => {
|
|
6980
|
+
const cfg = node.config;
|
|
6981
|
+
const scope2 = scopeFor(state, deps);
|
|
6982
|
+
const render = (key) => interpolate(str(cfg[key]), scope2).trim();
|
|
6983
|
+
const to = render("to");
|
|
6984
|
+
const from = render("from");
|
|
6985
|
+
const fromName = render("fromName");
|
|
6986
|
+
const replyTo = render("replyTo");
|
|
6987
|
+
const fail = (error, detail) => {
|
|
6988
|
+
state.nodeResults[node.id] = { ok: false, error, ...detail ? { detail } : {} };
|
|
6989
|
+
deps.events.note("email.failed", { node: node.id, error, ...detail ? { detail } : {} });
|
|
6990
|
+
return routeByPort(node, deps, "fail");
|
|
6991
|
+
};
|
|
6992
|
+
if (!isEmailAddress(to)) return fail("invalid_to", to ? "the recipient is not an email address" : "no recipient was captured");
|
|
6993
|
+
const resolver = deps.resolvers?.email;
|
|
6994
|
+
if (!resolver) {
|
|
6995
|
+
return fail(
|
|
6996
|
+
"email_unavailable",
|
|
6997
|
+
"email isn't sent here: a dry run, or a pool worker that couldn't get this call's workspace identity (worker token)"
|
|
6998
|
+
);
|
|
6999
|
+
}
|
|
7000
|
+
deps.events.note("email.sending", { node: node.id });
|
|
7001
|
+
const abort = new AbortController();
|
|
7002
|
+
let timer;
|
|
7003
|
+
try {
|
|
7004
|
+
const result = await Promise.race([
|
|
7005
|
+
resolver(
|
|
7006
|
+
{
|
|
7007
|
+
to,
|
|
7008
|
+
subject: render("subject"),
|
|
7009
|
+
text: interpolate(str(cfg.body), scope2),
|
|
7010
|
+
...from ? { from } : {},
|
|
7011
|
+
...fromName ? { fromName } : {},
|
|
7012
|
+
...replyTo ? { replyTo } : {}
|
|
7013
|
+
},
|
|
7014
|
+
{ signal: abort.signal }
|
|
7015
|
+
),
|
|
7016
|
+
new Promise((_, reject) => {
|
|
7017
|
+
timer = setTimeout(() => {
|
|
7018
|
+
abort.abort();
|
|
7019
|
+
reject(new EmailStepTimeout());
|
|
7020
|
+
}, EMAIL_SEND_TIMEOUT_MS);
|
|
7021
|
+
})
|
|
7022
|
+
]);
|
|
7023
|
+
if (!result.ok) return fail(result.error, result.detail);
|
|
7024
|
+
state.nodeResults[node.id] = { ok: true, emailId: result.emailId };
|
|
7025
|
+
deps.events.note("email.sent", { node: node.id, emailId: result.emailId });
|
|
7026
|
+
return routeByPort(node, deps, "ok");
|
|
7027
|
+
} catch (err) {
|
|
7028
|
+
if (err instanceof EmailStepTimeout) return fail("email_outcome_unknown", `no answer in ${EMAIL_SEND_TIMEOUT_MS} ms`);
|
|
7029
|
+
return fail("email_send_failed", err instanceof Error ? err.message : String(err));
|
|
7030
|
+
} finally {
|
|
7031
|
+
if (timer) clearTimeout(timer);
|
|
7032
|
+
}
|
|
7033
|
+
};
|
|
6903
7034
|
passthroughExecutor = async () => ({
|
|
6904
7035
|
kind: "advance"
|
|
6905
7036
|
});
|
|
@@ -6912,6 +7043,7 @@ ${collected}`);
|
|
|
6912
7043
|
handoff: handoffExecutor,
|
|
6913
7044
|
llm: llmExecutor,
|
|
6914
7045
|
tool: toolExecutor,
|
|
7046
|
+
email: emailExecutor,
|
|
6915
7047
|
set: setExecutor,
|
|
6916
7048
|
subflow: subflowExecutor,
|
|
6917
7049
|
decision: decisionExecutor,
|
|
@@ -8409,6 +8541,201 @@ function registryToolCapabilities(defs) {
|
|
|
8409
8541
|
return Object.entries(defs).map(([name, def]) => def.capability ?? `tool.${name}`);
|
|
8410
8542
|
}
|
|
8411
8543
|
|
|
8544
|
+
// src/errors.ts
|
|
8545
|
+
var VoiceLayerError = class extends Error {
|
|
8546
|
+
name = "VoiceLayerError";
|
|
8547
|
+
};
|
|
8548
|
+
var VoiceLayerAuthError = class extends VoiceLayerError {
|
|
8549
|
+
constructor(message, status) {
|
|
8550
|
+
super(message);
|
|
8551
|
+
this.status = status;
|
|
8552
|
+
}
|
|
8553
|
+
status;
|
|
8554
|
+
name = "VoiceLayerAuthError";
|
|
8555
|
+
};
|
|
8556
|
+
var VoiceLayerNetworkError = class extends VoiceLayerError {
|
|
8557
|
+
name = "VoiceLayerNetworkError";
|
|
8558
|
+
cause;
|
|
8559
|
+
constructor(message, cause) {
|
|
8560
|
+
super(message);
|
|
8561
|
+
this.cause = cause;
|
|
8562
|
+
}
|
|
8563
|
+
};
|
|
8564
|
+
var VoiceLayerValidationError = class extends VoiceLayerError {
|
|
8565
|
+
constructor(message, issues) {
|
|
8566
|
+
super(message);
|
|
8567
|
+
this.issues = issues;
|
|
8568
|
+
}
|
|
8569
|
+
issues;
|
|
8570
|
+
name = "VoiceLayerValidationError";
|
|
8571
|
+
};
|
|
8572
|
+
var VoiceLayerHttpError = class extends VoiceLayerError {
|
|
8573
|
+
constructor(message, status, body) {
|
|
8574
|
+
super(message);
|
|
8575
|
+
this.status = status;
|
|
8576
|
+
this.body = body;
|
|
8577
|
+
}
|
|
8578
|
+
status;
|
|
8579
|
+
body;
|
|
8580
|
+
name = "VoiceLayerHttpError";
|
|
8581
|
+
};
|
|
8582
|
+
var SendEmailResponse = z.object({
|
|
8583
|
+
emailId: z.string()
|
|
8584
|
+
}).passthrough();
|
|
8585
|
+
var EmailClient = class {
|
|
8586
|
+
constructor(transport) {
|
|
8587
|
+
this.transport = transport;
|
|
8588
|
+
}
|
|
8589
|
+
transport;
|
|
8590
|
+
/** `signal` aborts the request (the flow's Email step bounds its wait). A provider refusal (502), "email isn't set
|
|
8591
|
+
* up" (503) and "SES didn't answer — it may have sent" (504) are answers, never retried: a retry could only send
|
|
8592
|
+
* twice or fail again. */
|
|
8593
|
+
async send(input, opts = {}) {
|
|
8594
|
+
const body = {
|
|
8595
|
+
to: input.to,
|
|
8596
|
+
subject: input.subject,
|
|
8597
|
+
text: input.text,
|
|
8598
|
+
...input.html !== void 0 ? { html: input.html } : {},
|
|
8599
|
+
...input.from !== void 0 ? { from: input.from } : {},
|
|
8600
|
+
...input.fromName !== void 0 ? { fromName: input.fromName } : {},
|
|
8601
|
+
...input.replyTo !== void 0 ? { replyTo: input.replyTo } : {},
|
|
8602
|
+
...input.conversationId !== void 0 ? { conversationId: input.conversationId } : {}
|
|
8603
|
+
};
|
|
8604
|
+
const parsed = await this.transport.request(
|
|
8605
|
+
{ method: "POST", path: "/v1/email/send", body, noRetryStatuses: [502, 503, 504], ...opts.signal ? { signal: opts.signal } : {} },
|
|
8606
|
+
SendEmailResponse
|
|
8607
|
+
);
|
|
8608
|
+
return { emailId: parsed.emailId };
|
|
8609
|
+
}
|
|
8610
|
+
};
|
|
8611
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
8612
|
+
var DEFAULT_INITIAL_BACKOFF_MS = 250;
|
|
8613
|
+
function buildUrl(baseUrl, path, query) {
|
|
8614
|
+
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
8615
|
+
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
|
|
8616
|
+
let url = `${trimmedBase}${trimmedPath}`;
|
|
8617
|
+
if (query) {
|
|
8618
|
+
const params = new URLSearchParams();
|
|
8619
|
+
for (const [k, v] of Object.entries(query)) {
|
|
8620
|
+
if (v !== void 0) params.set(k, v);
|
|
8621
|
+
}
|
|
8622
|
+
const qs = params.toString();
|
|
8623
|
+
if (qs.length > 0) url += `?${qs}`;
|
|
8624
|
+
}
|
|
8625
|
+
return url;
|
|
8626
|
+
}
|
|
8627
|
+
async function sleep(ms, signal) {
|
|
8628
|
+
return new Promise((resolve, reject) => {
|
|
8629
|
+
if (signal?.aborted) {
|
|
8630
|
+
reject(new VoiceLayerNetworkError("aborted"));
|
|
8631
|
+
return;
|
|
8632
|
+
}
|
|
8633
|
+
const t = setTimeout(() => resolve(), ms);
|
|
8634
|
+
const onAbort = () => {
|
|
8635
|
+
clearTimeout(t);
|
|
8636
|
+
reject(new VoiceLayerNetworkError("aborted"));
|
|
8637
|
+
};
|
|
8638
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8639
|
+
});
|
|
8640
|
+
}
|
|
8641
|
+
var Transport = class {
|
|
8642
|
+
constructor(options) {
|
|
8643
|
+
this.options = options;
|
|
8644
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
8645
|
+
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
8646
|
+
this.initialBackoffMs = options.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
8647
|
+
}
|
|
8648
|
+
options;
|
|
8649
|
+
fetchImpl;
|
|
8650
|
+
maxRetries;
|
|
8651
|
+
initialBackoffMs;
|
|
8652
|
+
async request(opts, schema) {
|
|
8653
|
+
const url = buildUrl(this.options.baseUrl, opts.path, opts.query);
|
|
8654
|
+
const headers = {
|
|
8655
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
8656
|
+
"content-type": "application/json",
|
|
8657
|
+
accept: "application/json"
|
|
8658
|
+
};
|
|
8659
|
+
if (this.options.userAgent) {
|
|
8660
|
+
headers["user-agent"] = this.options.userAgent;
|
|
8661
|
+
}
|
|
8662
|
+
if (opts.headers) {
|
|
8663
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
8664
|
+
headers[k.toLowerCase()] = v;
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
const init = {
|
|
8668
|
+
method: opts.method,
|
|
8669
|
+
headers,
|
|
8670
|
+
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {},
|
|
8671
|
+
...opts.signal ? { signal: opts.signal } : {}
|
|
8672
|
+
};
|
|
8673
|
+
let lastError;
|
|
8674
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
8675
|
+
try {
|
|
8676
|
+
const res = await this.fetchImpl(url, init);
|
|
8677
|
+
if (res.status === 401 || res.status === 403) {
|
|
8678
|
+
const body = await safeJson(res);
|
|
8679
|
+
throw new VoiceLayerAuthError(
|
|
8680
|
+
extractMessage(body, `auth failed (${res.status})`),
|
|
8681
|
+
res.status
|
|
8682
|
+
);
|
|
8683
|
+
}
|
|
8684
|
+
if (res.status >= 500 && attempt < this.maxRetries && !opts.noRetryStatuses?.includes(res.status)) {
|
|
8685
|
+
lastError = new VoiceLayerHttpError(`server error ${res.status}`, res.status, await safeJson(res));
|
|
8686
|
+
await sleep(this.backoffMs(attempt), opts.signal);
|
|
8687
|
+
continue;
|
|
8688
|
+
}
|
|
8689
|
+
if (!res.ok) {
|
|
8690
|
+
const body = await safeJson(res);
|
|
8691
|
+
throw new VoiceLayerHttpError(extractMessage(body, `http ${res.status}`), res.status, body);
|
|
8692
|
+
}
|
|
8693
|
+
const json = await res.json();
|
|
8694
|
+
const parsed = schema.safeParse(json);
|
|
8695
|
+
if (!parsed.success) {
|
|
8696
|
+
throw new VoiceLayerValidationError("response failed schema validation", parsed.error.issues);
|
|
8697
|
+
}
|
|
8698
|
+
return parsed.data;
|
|
8699
|
+
} catch (err) {
|
|
8700
|
+
if (err instanceof VoiceLayerAuthError || err instanceof VoiceLayerValidationError || err instanceof VoiceLayerHttpError && (err.status < 500 || opts.noRetryStatuses?.includes(err.status) === true)) {
|
|
8701
|
+
throw err;
|
|
8702
|
+
}
|
|
8703
|
+
if (attempt >= this.maxRetries) {
|
|
8704
|
+
if (err instanceof VoiceLayerHttpError) throw err;
|
|
8705
|
+
throw new VoiceLayerNetworkError(
|
|
8706
|
+
err instanceof Error ? err.message : "network error",
|
|
8707
|
+
err
|
|
8708
|
+
);
|
|
8709
|
+
}
|
|
8710
|
+
lastError = err;
|
|
8711
|
+
await sleep(this.backoffMs(attempt), opts.signal);
|
|
8712
|
+
}
|
|
8713
|
+
}
|
|
8714
|
+
throw lastError instanceof Error ? lastError : new VoiceLayerNetworkError("exhausted retries");
|
|
8715
|
+
}
|
|
8716
|
+
backoffMs(attempt) {
|
|
8717
|
+
const base = this.initialBackoffMs * Math.pow(2, attempt);
|
|
8718
|
+
const jitter2 = Math.random() * base * 0.25;
|
|
8719
|
+
return Math.floor(base + jitter2);
|
|
8720
|
+
}
|
|
8721
|
+
};
|
|
8722
|
+
async function safeJson(res) {
|
|
8723
|
+
try {
|
|
8724
|
+
return await res.json();
|
|
8725
|
+
} catch {
|
|
8726
|
+
return null;
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
8729
|
+
function extractMessage(body, fallback) {
|
|
8730
|
+
if (body && typeof body === "object" && "message" in body && typeof body.message === "string") {
|
|
8731
|
+
return body.message;
|
|
8732
|
+
}
|
|
8733
|
+
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
8734
|
+
return body.error;
|
|
8735
|
+
}
|
|
8736
|
+
return fallback;
|
|
8737
|
+
}
|
|
8738
|
+
|
|
8412
8739
|
// src/runtime/graph/resolvers.ts
|
|
8413
8740
|
var KB_SEARCH_PATH = "/v1/knowledge/search";
|
|
8414
8741
|
function createKnowledgeResolver(opts) {
|
|
@@ -8492,6 +8819,50 @@ function createToolExecutor(opts) {
|
|
|
8492
8819
|
}
|
|
8493
8820
|
};
|
|
8494
8821
|
}
|
|
8822
|
+
function createEmailResolver(email, opts = {}) {
|
|
8823
|
+
return async (req, call) => {
|
|
8824
|
+
try {
|
|
8825
|
+
const sent = await email.send({ ...req, ...opts.conversationId ? { conversationId: opts.conversationId } : {} }, call?.signal ? { signal: call.signal } : {});
|
|
8826
|
+
return { ok: true, emailId: sent.emailId };
|
|
8827
|
+
} catch (err) {
|
|
8828
|
+
if (err instanceof VoiceLayerHttpError) {
|
|
8829
|
+
const body = err.body ?? {};
|
|
8830
|
+
const detail = typeof body.message === "string" ? body.message : typeof body.detail === "string" ? body.detail : void 0;
|
|
8831
|
+
return { ok: false, error: typeof body.error === "string" ? body.error : `http_${err.status}`, ...detail ? { detail } : {} };
|
|
8832
|
+
}
|
|
8833
|
+
return { ok: false, error: "email_outcome_unknown", detail: err instanceof Error ? err.message : String(err) };
|
|
8834
|
+
}
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
8837
|
+
var NOT_SENT = /* @__PURE__ */ new Set([
|
|
8838
|
+
"invalid_email",
|
|
8839
|
+
"invalid_body",
|
|
8840
|
+
"from_not_verified",
|
|
8841
|
+
"email_rate_limited",
|
|
8842
|
+
"email_conversation_limit",
|
|
8843
|
+
"email_unavailable",
|
|
8844
|
+
"email_provider_error",
|
|
8845
|
+
"email_send_failed",
|
|
8846
|
+
"insufficient_balance",
|
|
8847
|
+
"spend_cap_exceeded"
|
|
8848
|
+
]);
|
|
8849
|
+
function dedupeConversationEmails(resolver) {
|
|
8850
|
+
const sends = /* @__PURE__ */ new Map();
|
|
8851
|
+
return (req, opts) => {
|
|
8852
|
+
const body = createHash("sha256").update(req.text).digest("hex");
|
|
8853
|
+
const key = `${req.to.trim().toLowerCase()}
|
|
8854
|
+
${req.subject}
|
|
8855
|
+
${body}`;
|
|
8856
|
+
const prior = sends.get(key);
|
|
8857
|
+
if (prior) return prior;
|
|
8858
|
+
const sending = resolver(req, opts).then((result) => {
|
|
8859
|
+
if (!result.ok && NOT_SENT.has(result.error)) sends.delete(key);
|
|
8860
|
+
return result;
|
|
8861
|
+
});
|
|
8862
|
+
sends.set(key, sending);
|
|
8863
|
+
return sending;
|
|
8864
|
+
};
|
|
8865
|
+
}
|
|
8495
8866
|
var PLAYBOOKS_PATH = "/v1/playbooks";
|
|
8496
8867
|
function toPlaybookResolution(pb) {
|
|
8497
8868
|
const requiredFields = Array.from(
|
|
@@ -8637,7 +9008,7 @@ function createCodeResolver(opts) {
|
|
|
8637
9008
|
}
|
|
8638
9009
|
};
|
|
8639
9010
|
}
|
|
8640
|
-
function buildGraphResolvers(credential2) {
|
|
9011
|
+
function buildGraphResolvers(credential2, call = {}) {
|
|
8641
9012
|
const internalToken = process.env["INTERNAL_SERVICE_TOKEN"];
|
|
8642
9013
|
const resolvers = {};
|
|
8643
9014
|
resolvers.code = createCodeResolver();
|
|
@@ -8647,6 +9018,8 @@ function buildGraphResolvers(credential2) {
|
|
|
8647
9018
|
}
|
|
8648
9019
|
if (credential2) {
|
|
8649
9020
|
const { baseUrl, apiKey } = credential2;
|
|
9021
|
+
const email = new EmailClient(new Transport({ baseUrl, apiKey }));
|
|
9022
|
+
resolvers.email = dedupeConversationEmails(createEmailResolver(email, call.conversationId ? { conversationId: call.conversationId } : {}));
|
|
8650
9023
|
resolvers.knowledge = createKnowledgeResolver({ baseUrl, apiKey });
|
|
8651
9024
|
resolvers.playbooks = createPlaybookResolver({ baseUrl, apiKey });
|
|
8652
9025
|
resolvers.flows = createFlowResolver({ baseUrl, apiKey });
|
|
@@ -10122,174 +10495,6 @@ async function runTextTranscript(opts) {
|
|
|
10122
10495
|
return { replies, outcome, trace: trace8 };
|
|
10123
10496
|
}
|
|
10124
10497
|
|
|
10125
|
-
// src/errors.ts
|
|
10126
|
-
var VoiceLayerError = class extends Error {
|
|
10127
|
-
name = "VoiceLayerError";
|
|
10128
|
-
};
|
|
10129
|
-
var VoiceLayerAuthError = class extends VoiceLayerError {
|
|
10130
|
-
constructor(message, status) {
|
|
10131
|
-
super(message);
|
|
10132
|
-
this.status = status;
|
|
10133
|
-
}
|
|
10134
|
-
status;
|
|
10135
|
-
name = "VoiceLayerAuthError";
|
|
10136
|
-
};
|
|
10137
|
-
var VoiceLayerNetworkError = class extends VoiceLayerError {
|
|
10138
|
-
name = "VoiceLayerNetworkError";
|
|
10139
|
-
cause;
|
|
10140
|
-
constructor(message, cause) {
|
|
10141
|
-
super(message);
|
|
10142
|
-
this.cause = cause;
|
|
10143
|
-
}
|
|
10144
|
-
};
|
|
10145
|
-
var VoiceLayerValidationError = class extends VoiceLayerError {
|
|
10146
|
-
constructor(message, issues) {
|
|
10147
|
-
super(message);
|
|
10148
|
-
this.issues = issues;
|
|
10149
|
-
}
|
|
10150
|
-
issues;
|
|
10151
|
-
name = "VoiceLayerValidationError";
|
|
10152
|
-
};
|
|
10153
|
-
var VoiceLayerHttpError = class extends VoiceLayerError {
|
|
10154
|
-
constructor(message, status, body) {
|
|
10155
|
-
super(message);
|
|
10156
|
-
this.status = status;
|
|
10157
|
-
this.body = body;
|
|
10158
|
-
}
|
|
10159
|
-
status;
|
|
10160
|
-
body;
|
|
10161
|
-
name = "VoiceLayerHttpError";
|
|
10162
|
-
};
|
|
10163
|
-
|
|
10164
|
-
// src/transport.ts
|
|
10165
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
10166
|
-
var DEFAULT_INITIAL_BACKOFF_MS = 250;
|
|
10167
|
-
function buildUrl(baseUrl, path, query) {
|
|
10168
|
-
const trimmedBase = baseUrl.replace(/\/+$/, "");
|
|
10169
|
-
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
|
|
10170
|
-
let url = `${trimmedBase}${trimmedPath}`;
|
|
10171
|
-
if (query) {
|
|
10172
|
-
const params = new URLSearchParams();
|
|
10173
|
-
for (const [k, v] of Object.entries(query)) {
|
|
10174
|
-
if (v !== void 0) params.set(k, v);
|
|
10175
|
-
}
|
|
10176
|
-
const qs = params.toString();
|
|
10177
|
-
if (qs.length > 0) url += `?${qs}`;
|
|
10178
|
-
}
|
|
10179
|
-
return url;
|
|
10180
|
-
}
|
|
10181
|
-
async function sleep(ms, signal) {
|
|
10182
|
-
return new Promise((resolve, reject) => {
|
|
10183
|
-
if (signal?.aborted) {
|
|
10184
|
-
reject(new VoiceLayerNetworkError("aborted"));
|
|
10185
|
-
return;
|
|
10186
|
-
}
|
|
10187
|
-
const t = setTimeout(() => resolve(), ms);
|
|
10188
|
-
const onAbort = () => {
|
|
10189
|
-
clearTimeout(t);
|
|
10190
|
-
reject(new VoiceLayerNetworkError("aborted"));
|
|
10191
|
-
};
|
|
10192
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
10193
|
-
});
|
|
10194
|
-
}
|
|
10195
|
-
var Transport = class {
|
|
10196
|
-
constructor(options) {
|
|
10197
|
-
this.options = options;
|
|
10198
|
-
this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
10199
|
-
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
10200
|
-
this.initialBackoffMs = options.initialBackoffMs ?? DEFAULT_INITIAL_BACKOFF_MS;
|
|
10201
|
-
}
|
|
10202
|
-
options;
|
|
10203
|
-
fetchImpl;
|
|
10204
|
-
maxRetries;
|
|
10205
|
-
initialBackoffMs;
|
|
10206
|
-
async request(opts, schema) {
|
|
10207
|
-
const url = buildUrl(this.options.baseUrl, opts.path, opts.query);
|
|
10208
|
-
const headers = {
|
|
10209
|
-
authorization: `Bearer ${this.options.apiKey}`,
|
|
10210
|
-
"content-type": "application/json",
|
|
10211
|
-
accept: "application/json"
|
|
10212
|
-
};
|
|
10213
|
-
if (this.options.userAgent) {
|
|
10214
|
-
headers["user-agent"] = this.options.userAgent;
|
|
10215
|
-
}
|
|
10216
|
-
if (opts.headers) {
|
|
10217
|
-
for (const [k, v] of Object.entries(opts.headers)) {
|
|
10218
|
-
headers[k.toLowerCase()] = v;
|
|
10219
|
-
}
|
|
10220
|
-
}
|
|
10221
|
-
const init = {
|
|
10222
|
-
method: opts.method,
|
|
10223
|
-
headers,
|
|
10224
|
-
...opts.body !== void 0 ? { body: JSON.stringify(opts.body) } : {},
|
|
10225
|
-
...opts.signal ? { signal: opts.signal } : {}
|
|
10226
|
-
};
|
|
10227
|
-
let lastError;
|
|
10228
|
-
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
10229
|
-
try {
|
|
10230
|
-
const res = await this.fetchImpl(url, init);
|
|
10231
|
-
if (res.status === 401 || res.status === 403) {
|
|
10232
|
-
const body = await safeJson(res);
|
|
10233
|
-
throw new VoiceLayerAuthError(
|
|
10234
|
-
extractMessage(body, `auth failed (${res.status})`),
|
|
10235
|
-
res.status
|
|
10236
|
-
);
|
|
10237
|
-
}
|
|
10238
|
-
if (res.status >= 500 && attempt < this.maxRetries) {
|
|
10239
|
-
lastError = new VoiceLayerHttpError(`server error ${res.status}`, res.status, await safeJson(res));
|
|
10240
|
-
await sleep(this.backoffMs(attempt), opts.signal);
|
|
10241
|
-
continue;
|
|
10242
|
-
}
|
|
10243
|
-
if (!res.ok) {
|
|
10244
|
-
const body = await safeJson(res);
|
|
10245
|
-
throw new VoiceLayerHttpError(extractMessage(body, `http ${res.status}`), res.status, body);
|
|
10246
|
-
}
|
|
10247
|
-
const json = await res.json();
|
|
10248
|
-
const parsed = schema.safeParse(json);
|
|
10249
|
-
if (!parsed.success) {
|
|
10250
|
-
throw new VoiceLayerValidationError("response failed schema validation", parsed.error.issues);
|
|
10251
|
-
}
|
|
10252
|
-
return parsed.data;
|
|
10253
|
-
} catch (err) {
|
|
10254
|
-
if (err instanceof VoiceLayerAuthError || err instanceof VoiceLayerValidationError || err instanceof VoiceLayerHttpError && err.status < 500) {
|
|
10255
|
-
throw err;
|
|
10256
|
-
}
|
|
10257
|
-
if (attempt >= this.maxRetries) {
|
|
10258
|
-
if (err instanceof VoiceLayerHttpError) throw err;
|
|
10259
|
-
throw new VoiceLayerNetworkError(
|
|
10260
|
-
err instanceof Error ? err.message : "network error",
|
|
10261
|
-
err
|
|
10262
|
-
);
|
|
10263
|
-
}
|
|
10264
|
-
lastError = err;
|
|
10265
|
-
await sleep(this.backoffMs(attempt), opts.signal);
|
|
10266
|
-
}
|
|
10267
|
-
}
|
|
10268
|
-
throw lastError instanceof Error ? lastError : new VoiceLayerNetworkError("exhausted retries");
|
|
10269
|
-
}
|
|
10270
|
-
backoffMs(attempt) {
|
|
10271
|
-
const base = this.initialBackoffMs * Math.pow(2, attempt);
|
|
10272
|
-
const jitter2 = Math.random() * base * 0.25;
|
|
10273
|
-
return Math.floor(base + jitter2);
|
|
10274
|
-
}
|
|
10275
|
-
};
|
|
10276
|
-
async function safeJson(res) {
|
|
10277
|
-
try {
|
|
10278
|
-
return await res.json();
|
|
10279
|
-
} catch {
|
|
10280
|
-
return null;
|
|
10281
|
-
}
|
|
10282
|
-
}
|
|
10283
|
-
function extractMessage(body, fallback) {
|
|
10284
|
-
if (body && typeof body === "object" && "message" in body && typeof body.message === "string") {
|
|
10285
|
-
return body.message;
|
|
10286
|
-
}
|
|
10287
|
-
if (body && typeof body === "object" && "error" in body && typeof body.error === "string") {
|
|
10288
|
-
return body.error;
|
|
10289
|
-
}
|
|
10290
|
-
return fallback;
|
|
10291
|
-
}
|
|
10292
|
-
|
|
10293
10498
|
// src/agents.ts
|
|
10294
10499
|
init_src();
|
|
10295
10500
|
function collectDefaultMetadata() {
|
|
@@ -11138,28 +11343,6 @@ var FormsClient = class {
|
|
|
11138
11343
|
);
|
|
11139
11344
|
}
|
|
11140
11345
|
};
|
|
11141
|
-
var SendEmailResponse = z.object({
|
|
11142
|
-
emailId: z.string()
|
|
11143
|
-
}).passthrough();
|
|
11144
|
-
var EmailClient = class {
|
|
11145
|
-
constructor(transport) {
|
|
11146
|
-
this.transport = transport;
|
|
11147
|
-
}
|
|
11148
|
-
transport;
|
|
11149
|
-
async send(input) {
|
|
11150
|
-
const body = {
|
|
11151
|
-
to: input.to,
|
|
11152
|
-
subject: input.subject,
|
|
11153
|
-
text: input.text,
|
|
11154
|
-
...input.html !== void 0 ? { html: input.html } : {}
|
|
11155
|
-
};
|
|
11156
|
-
const parsed = await this.transport.request(
|
|
11157
|
-
{ method: "POST", path: "/v1/email/send", body },
|
|
11158
|
-
SendEmailResponse
|
|
11159
|
-
);
|
|
11160
|
-
return { emailId: parsed.emailId };
|
|
11161
|
-
}
|
|
11162
|
-
};
|
|
11163
11346
|
|
|
11164
11347
|
// src/plans.ts
|
|
11165
11348
|
init_src();
|
|
@@ -14746,6 +14929,24 @@ function guardedRegistryRun(run, router, observe) {
|
|
|
14746
14929
|
return result;
|
|
14747
14930
|
};
|
|
14748
14931
|
}
|
|
14932
|
+
function auditedEmailResolver(resolver, observe) {
|
|
14933
|
+
return async (req, opts) => {
|
|
14934
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
14935
|
+
const result = await resolver(req, opts);
|
|
14936
|
+
observe({
|
|
14937
|
+
invocationId: randomUUID(),
|
|
14938
|
+
toolName: "flow.email",
|
|
14939
|
+
params: { to: req.to, subject: req.subject, ...req.from ? { from: req.from } : {} },
|
|
14940
|
+
ok: result.ok,
|
|
14941
|
+
blocked: false,
|
|
14942
|
+
result: result.ok ? { emailId: result.emailId } : { error: result.error },
|
|
14943
|
+
...result.ok ? {} : { errorMessage: result.detail ?? result.error },
|
|
14944
|
+
startedAt,
|
|
14945
|
+
durationMs: Date.now() - startedAt.getTime()
|
|
14946
|
+
});
|
|
14947
|
+
return result;
|
|
14948
|
+
};
|
|
14949
|
+
}
|
|
14749
14950
|
|
|
14750
14951
|
// src/runtime/call-room.ts
|
|
14751
14952
|
init_src();
|
|
@@ -18112,9 +18313,14 @@ ${apology}`
|
|
|
18112
18313
|
} else if (this.config.onCall) {
|
|
18113
18314
|
await this.config.onCall(ctx);
|
|
18114
18315
|
} else if (graphMode && graphProgram && graphEvents) {
|
|
18115
|
-
const builtResolvers = buildGraphResolvers(callCredential);
|
|
18316
|
+
const builtResolvers = buildGraphResolvers(callCredential, { conversationId: ctx.call.callId });
|
|
18116
18317
|
const registry = builtResolvers?.registryTools;
|
|
18117
|
-
const
|
|
18318
|
+
const email = builtResolvers?.email;
|
|
18319
|
+
const graphResolvers = builtResolvers ? {
|
|
18320
|
+
...builtResolvers,
|
|
18321
|
+
...registry ? { registryTools: { catalog: () => registry.catalog(), run: guardedRegistryRun((n, a) => registry.run(n, a), toolkit.router, toolAudit) } } : {},
|
|
18322
|
+
...email ? { email: auditedEmailResolver(email, toolAudit) } : {}
|
|
18323
|
+
} : builtResolvers;
|
|
18118
18324
|
const outcome = await runFlowProgram(graphProgram, ctx, {
|
|
18119
18325
|
processRt,
|
|
18120
18326
|
session,
|