@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.
@@ -1,4 +1,4 @@
1
- export { b0 as FlowResolver, b1 as GraphResolvers, b2 as GraphTraceEvent, b3 as KnowledgeResolver, b4 as LiveTextConversation, b5 as LiveTurnResult, b6 as PlaybookLibraryEntry, b7 as PlaybookResolver, b8 as RegistryToolsResolver, b9 as RunLiveTextOptions, af as RunTextSessionOptions, ag as RunTextTranscriptOptions, ax as TextSession, ay as TextTranscriptResult, o as ToolExecutor, ba as denyPrivateNetwork, bb as privateNetworkOptInHonoured, bc as runLiveTextConversation, aY as runTextSession, aZ as runTextTranscript, bd as runWithOpenAIScope, be as toPlaybookResolution } from '../text-session-Bf4Jni7H.js';
1
+ export { b0 as EmailResolver, b1 as FlowResolver, b2 as GraphResolvers, b3 as GraphTraceEvent, b4 as KnowledgeResolver, b5 as LiveTextConversation, b6 as LiveTurnResult, b7 as PlaybookLibraryEntry, b8 as PlaybookResolver, b9 as RegistryToolsResolver, ba as RunLiveTextOptions, af as RunTextSessionOptions, ag as RunTextTranscriptOptions, ax as TextSession, ay as TextTranscriptResult, o as ToolExecutor, bb as dedupeConversationEmails, bc as denyPrivateNetwork, bd as privateNetworkOptInHonoured, be as runLiveTextConversation, aY as runTextSession, aZ as runTextTranscript, bf as runWithOpenAIScope, bg as toPlaybookResolution } from '../text-session-BbG3VG50.js';
2
2
  import '@livekit/agents';
3
3
  import 'zod';
4
4
  import '../types-KqrAfY85.js';
@@ -9,8 +9,8 @@ import https from 'https';
9
9
  import { Readable } from 'stream';
10
10
  import { lookup } from 'dns/promises';
11
11
  import 'quickjs-emscripten';
12
+ import { createHash, randomUUID } from 'crypto';
12
13
  import { setup, createActor } from 'xstate';
13
- import { randomUUID } from 'crypto';
14
14
 
15
15
  var __defProp = Object.defineProperty;
16
16
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -238,6 +238,8 @@ var init_flow = __esm({
238
238
  "ask",
239
239
  "confirm",
240
240
  "tool",
241
+ // Send an email through the workspace's email (metered, wallet-gated; From on a verified domain) — burn-down G-22.
242
+ "email",
241
243
  "decision",
242
244
  "handoff",
243
245
  "end",
@@ -1255,6 +1257,61 @@ var init_forms = __esm({
1255
1257
  "../contracts/src/forms.ts"() {
1256
1258
  }
1257
1259
  });
1260
+ function isEmailAddress(value) {
1261
+ return EMAIL_RE.test(value) && value.length <= 254;
1262
+ }
1263
+ function normalizeDomain(input) {
1264
+ return input.trim().toLowerCase().replace(/^[a-z]+:\/\//, "").replace(/\/.*$/, "").replace(/\.$/, "");
1265
+ }
1266
+ function isDomainName(value) {
1267
+ return DOMAIN_RE.test(value);
1268
+ }
1269
+ var EMAIL_RE, EMAIL_SUBJECT_MAX, EMAIL_TEXT_MAX, EMAIL_HTML_MAX, address, DOMAIN_RE, EmailDomainStatus, EmailDnsRecord;
1270
+ var init_email = __esm({
1271
+ "../contracts/src/email.ts"() {
1272
+ EMAIL_RE = /^[^\s@<>"]+@[^\s@<>"]+\.[^\s@<>"]+$/;
1273
+ EMAIL_SUBJECT_MAX = 200;
1274
+ EMAIL_TEXT_MAX = 5e4;
1275
+ EMAIL_HTML_MAX = 2e5;
1276
+ address = z.string().max(254).refine(isEmailAddress, { message: "must be an email address" });
1277
+ z.object({
1278
+ to: address,
1279
+ subject: z.string().min(1).max(EMAIL_SUBJECT_MAX),
1280
+ text: z.string().min(1).max(EMAIL_TEXT_MAX),
1281
+ html: z.string().min(1).max(EMAIL_HTML_MAX).optional(),
1282
+ // An address on one of the workspace's verified sending domains; omitted → the platform sender.
1283
+ from: address.optional(),
1284
+ // The display name shown beside `from` (or beside the platform sender's address).
1285
+ fromName: z.string().trim().min(1).max(64).refine((v) => !/[\r\n<>"]/.test(v), { message: 'must not contain line breaks, <, > or "' }).optional(),
1286
+ // Where a reply goes — any address; it needs no verification.
1287
+ replyTo: address.optional(),
1288
+ // The conversation (a call, a text conversation) the email belongs to — a conversation sends at most 3 emails.
1289
+ conversationId: z.string().min(1).max(128).optional()
1290
+ });
1291
+ DOMAIN_RE = /^(?=.{4,253}$)(?!-)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/;
1292
+ z.object({
1293
+ domain: z.string().max(253).transform(normalizeDomain).refine(isDomainName, { message: "must be a domain like example.com" })
1294
+ });
1295
+ EmailDomainStatus = z.enum(["pending", "verified", "failed"]);
1296
+ EmailDnsRecord = z.object({
1297
+ type: z.enum(["CNAME", "TXT"]),
1298
+ name: z.string(),
1299
+ value: z.string(),
1300
+ // dkim: proves the domain (required); dmarc: recommended, only when the domain has no DMARC record yet.
1301
+ purpose: z.enum(["dkim", "dmarc"]),
1302
+ required: z.boolean()
1303
+ });
1304
+ z.object({
1305
+ id: z.string(),
1306
+ domain: z.string(),
1307
+ status: EmailDomainStatus,
1308
+ records: z.array(EmailDnsRecord),
1309
+ createdAt: z.string().datetime(),
1310
+ lastCheckedAt: z.string().datetime().nullable(),
1311
+ verifiedAt: z.string().datetime().nullable()
1312
+ });
1313
+ }
1314
+ });
1258
1315
  var AgentConfigVersionDTO, AgentConfigDiffField, AgentConfigChange;
1259
1316
  var init_agent_versions = __esm({
1260
1317
  "../contracts/src/agent-versions.ts"() {
@@ -2003,6 +2060,7 @@ var init_flow_validate = __esm({
2003
2060
  init_secret_headers();
2004
2061
  init_network_address();
2005
2062
  init_dispatch_metadata();
2063
+ init_email();
2006
2064
  init_flow_edges();
2007
2065
  FlowIssueSeverity = z.enum(["error", "warning"]);
2008
2066
  FlowIssue = z.object({
@@ -3776,6 +3834,7 @@ var init_src = __esm({
3776
3834
  init_memory();
3777
3835
  init_participants();
3778
3836
  init_forms();
3837
+ init_email();
3779
3838
  init_consultation();
3780
3839
  init_agent_config();
3781
3840
  init_agent_versions();
@@ -5228,7 +5287,7 @@ function menuDigitOf(b) {
5228
5287
  function menuLabelOf(label) {
5229
5288
  return label.replace(/^\s*[0-9*#]\s*[-—–:.]*\s*/, "").trim() || label.trim();
5230
5289
  }
5231
- 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;
5290
+ 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;
5232
5291
  var init_executors = __esm({
5233
5292
  "src/runtime/graph/executors.ts"() {
5234
5293
  init_log();
@@ -6042,6 +6101,64 @@ ${collected}`);
6042
6101
  if (outcome.kind === "handoff") return { kind: "handoff", to: outcome.to };
6043
6102
  return { kind: "advance" };
6044
6103
  };
6104
+ EmailStepTimeout = class extends Error {
6105
+ };
6106
+ EMAIL_SEND_TIMEOUT_MS = 1e4;
6107
+ emailExecutor = async (node, state, deps) => {
6108
+ const cfg = node.config;
6109
+ const scope2 = scopeFor(state, deps);
6110
+ const render = (key) => interpolate(str(cfg[key]), scope2).trim();
6111
+ const to = render("to");
6112
+ const from = render("from");
6113
+ const fromName = render("fromName");
6114
+ const replyTo = render("replyTo");
6115
+ const fail = (error, detail) => {
6116
+ state.nodeResults[node.id] = { ok: false, error, ...detail ? { detail } : {} };
6117
+ deps.events.note("email.failed", { node: node.id, error, ...detail ? { detail } : {} });
6118
+ return routeByPort(node, deps, "fail");
6119
+ };
6120
+ if (!isEmailAddress(to)) return fail("invalid_to", to ? "the recipient is not an email address" : "no recipient was captured");
6121
+ const resolver = deps.resolvers?.email;
6122
+ if (!resolver) {
6123
+ return fail(
6124
+ "email_unavailable",
6125
+ "email isn't sent here: a dry run, or a pool worker that couldn't get this call's workspace identity (worker token)"
6126
+ );
6127
+ }
6128
+ deps.events.note("email.sending", { node: node.id });
6129
+ const abort = new AbortController();
6130
+ let timer;
6131
+ try {
6132
+ const result = await Promise.race([
6133
+ resolver(
6134
+ {
6135
+ to,
6136
+ subject: render("subject"),
6137
+ text: interpolate(str(cfg.body), scope2),
6138
+ ...from ? { from } : {},
6139
+ ...fromName ? { fromName } : {},
6140
+ ...replyTo ? { replyTo } : {}
6141
+ },
6142
+ { signal: abort.signal }
6143
+ ),
6144
+ new Promise((_, reject) => {
6145
+ timer = setTimeout(() => {
6146
+ abort.abort();
6147
+ reject(new EmailStepTimeout());
6148
+ }, EMAIL_SEND_TIMEOUT_MS);
6149
+ })
6150
+ ]);
6151
+ if (!result.ok) return fail(result.error, result.detail);
6152
+ state.nodeResults[node.id] = { ok: true, emailId: result.emailId };
6153
+ deps.events.note("email.sent", { node: node.id, emailId: result.emailId });
6154
+ return routeByPort(node, deps, "ok");
6155
+ } catch (err) {
6156
+ if (err instanceof EmailStepTimeout) return fail("email_outcome_unknown", `no answer in ${EMAIL_SEND_TIMEOUT_MS} ms`);
6157
+ return fail("email_send_failed", err instanceof Error ? err.message : String(err));
6158
+ } finally {
6159
+ if (timer) clearTimeout(timer);
6160
+ }
6161
+ };
6045
6162
  passthroughExecutor = async () => ({
6046
6163
  kind: "advance"
6047
6164
  });
@@ -6054,6 +6171,7 @@ ${collected}`);
6054
6171
  handoff: handoffExecutor,
6055
6172
  llm: llmExecutor,
6056
6173
  tool: toolExecutor,
6174
+ email: emailExecutor,
6057
6175
  set: setExecutor,
6058
6176
  subflow: subflowExecutor,
6059
6177
  decision: decisionExecutor,
@@ -6716,6 +6834,40 @@ init_graph_model();
6716
6834
  init_src();
6717
6835
  init_embedder();
6718
6836
  init_llm();
6837
+ z.object({
6838
+ emailId: z.string()
6839
+ }).passthrough();
6840
+
6841
+ // src/runtime/graph/resolvers.ts
6842
+ var NOT_SENT = /* @__PURE__ */ new Set([
6843
+ "invalid_email",
6844
+ "invalid_body",
6845
+ "from_not_verified",
6846
+ "email_rate_limited",
6847
+ "email_conversation_limit",
6848
+ "email_unavailable",
6849
+ "email_provider_error",
6850
+ "email_send_failed",
6851
+ "insufficient_balance",
6852
+ "spend_cap_exceeded"
6853
+ ]);
6854
+ function dedupeConversationEmails(resolver) {
6855
+ const sends = /* @__PURE__ */ new Map();
6856
+ return (req, opts) => {
6857
+ const body = createHash("sha256").update(req.text).digest("hex");
6858
+ const key = `${req.to.trim().toLowerCase()}
6859
+ ${req.subject}
6860
+ ${body}`;
6861
+ const prior = sends.get(key);
6862
+ if (prior) return prior;
6863
+ const sending = resolver(req, opts).then((result) => {
6864
+ if (!result.ok && NOT_SENT.has(result.error)) sends.delete(key);
6865
+ return result;
6866
+ });
6867
+ sends.set(key, sending);
6868
+ return sending;
6869
+ };
6870
+ }
6719
6871
  function toPlaybookResolution(pb) {
6720
6872
  const requiredFields = Array.from(
6721
6873
  new Set((pb.exitConditions ?? []).flatMap((e) => e.requiredVariables ?? []))
@@ -8044,4 +8196,4 @@ function runLiveTextConversation(opts) {
8044
8196
  };
8045
8197
  }
8046
8198
 
8047
- export { denyPrivateNetwork, privateNetworkOptInHonoured, runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope, toPlaybookResolution };
8199
+ export { dedupeConversationEmails, denyPrivateNetwork, privateNetworkOptInHonoured, runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope, toPlaybookResolution };