@voicelayer/sdk 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3133,7 +3133,7 @@ var init_executors = __esm({
3133
3133
  const authoredSlot = slot === node.id ? "" : slot.trim();
3134
3134
  const label = authoredSlot.replace(/[_-]+/g, " ");
3135
3135
  if (hasSlot(deps.processRt.getData(), slot)) {
3136
- console.log("[graph] ask: slot already known, skipping", { slot: slot.trim() });
3136
+ deps.events.note("ask.skipped", { slot: slot.trim(), reason: "already known" });
3137
3137
  return { kind: "advance" };
3138
3138
  }
3139
3139
  const askText = interpolate(
@@ -3147,10 +3147,10 @@ var init_executors = __esm({
3147
3147
  state.lastUtterance = t.text;
3148
3148
  state.lastDtmf = t.dtmf;
3149
3149
  if (hasSlot(deps.processRt.getData(), slot)) {
3150
- console.log("[graph] ask: captured", { slot: slot.trim() });
3150
+ deps.events.note("ask.captured", { slot: slot.trim() });
3151
3151
  return { kind: "advance" };
3152
3152
  }
3153
- console.log("[graph] ask: clarify", { slot: slot.trim(), turn });
3153
+ deps.events.note("ask.clarify", { slot: slot.trim(), turn });
3154
3154
  await withHistory(
3155
3155
  deps,
3156
3156
  historyPolicyOf(node),
@@ -3251,11 +3251,18 @@ ${collected}`);
3251
3251
  if (ok && cfg.outputAs && typeof cfg.outputAs === "object") {
3252
3252
  for (const [slot, path] of Object.entries(cfg.outputAs)) {
3253
3253
  if (typeof path !== "string" || !path.trim()) continue;
3254
- const value = resolvePath(result, path.trim());
3255
- if (value !== void 0 && value !== null) {
3256
- deps.processRt.captureField(slot, value);
3257
- deps.events.note("tool.output_captured", { node: node.id, slot });
3254
+ const raw = resolvePath(result, path.trim());
3255
+ if (raw === void 0 || raw === null) {
3256
+ deps.events.note("tool.output_missing", { node: node.id, slot, path });
3257
+ continue;
3258
3258
  }
3259
+ const value = typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint" ? String(raw) : JSON.stringify(raw);
3260
+ const captured = deps.processRt.captureField(slot, value);
3261
+ deps.events.note(captured ? "tool.output_captured" : "tool.output_rejected", {
3262
+ node: node.id,
3263
+ slot,
3264
+ ...captured ? {} : { reason: "slot not declared or value not coercible" }
3265
+ });
3259
3266
  }
3260
3267
  }
3261
3268
  return routeByPort(node, deps, ok ? "ok" : "fail");
@@ -3764,13 +3771,19 @@ ${collected}`);
3764
3771
  setExecutor = async (node, state, deps) => {
3765
3772
  const assignments = Array.isArray(node.config.assignments) ? node.config.assignments : [];
3766
3773
  let applied = 0;
3774
+ const rejected = [];
3767
3775
  for (const a of assignments) {
3768
3776
  if (!a || typeof a.name !== "string" || !a.name.trim()) continue;
3777
+ const name = a.name.trim();
3769
3778
  const value = interpolate(typeof a.value === "string" ? a.value : "", scopeFor(state, deps));
3770
- deps.processRt.captureField(a.name.trim(), value);
3771
- applied += 1;
3779
+ if (deps.processRt.captureField(name, value)) applied += 1;
3780
+ else rejected.push(name);
3772
3781
  }
3773
- deps.events.note("set.applied", { node: node.id, count: applied });
3782
+ deps.events.note("set.applied", {
3783
+ node: node.id,
3784
+ count: applied,
3785
+ ...rejected.length ? { rejected } : {}
3786
+ });
3774
3787
  return { kind: "advance" };
3775
3788
  };
3776
3789
  MAX_SUBFLOW_DEPTH = 3;
@@ -7365,6 +7378,125 @@ function createTextSessionAdapter(transport) {
7365
7378
  return { adapter, push, setState, close };
7366
7379
  }
7367
7380
 
7381
+ // src/flow-runtime.ts
7382
+ init_define_tool();
7383
+ function processSchemaToDefinition(schema) {
7384
+ const required = new Set(schema.completionGate.requiredFields);
7385
+ const collect = {};
7386
+ for (const f of schema.fields) {
7387
+ collect[f.name] = {
7388
+ type: f.type,
7389
+ required: required.has(f.name) || f.required,
7390
+ ...f.ask !== void 0 ? { ask: f.ask } : {},
7391
+ ...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
7392
+ ...f.min !== void 0 ? { min: f.min } : {},
7393
+ ...f.max !== void 0 ? { max: f.max } : {},
7394
+ ...f.enum !== void 0 ? { enum: f.enum } : {}
7395
+ };
7396
+ }
7397
+ return {
7398
+ collect,
7399
+ completeWhen: "all-required-captured",
7400
+ ...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
7401
+ };
7402
+ }
7403
+ var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
7404
+ function toTriggerCondition(on) {
7405
+ return on.startsWith("signal:") ? on : new RegExp(on, "i");
7406
+ }
7407
+ function toTriggerAction(then, graphNodeIds) {
7408
+ if (then === "endCall" || then === "end") return "endCall";
7409
+ if (then === "handoff") return "handoff";
7410
+ if (then.startsWith("say:")) return { say: then.slice(4).trim() };
7411
+ if (graphNodeIds?.has(then)) return { goto: then };
7412
+ return "handoff";
7413
+ }
7414
+ function processSchemaToAgentParts(schema, opts = {}) {
7415
+ const process2 = processSchemaToDefinition(schema);
7416
+ const promptParts = [];
7417
+ if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
7418
+ for (const p of schema.prompts ?? []) {
7419
+ if (p.text.trim()) promptParts.push(p.text.trim());
7420
+ }
7421
+ const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
7422
+ let triggers;
7423
+ if (schema.triggers && schema.triggers.length > 0) {
7424
+ triggers = {};
7425
+ for (const t of schema.triggers) {
7426
+ triggers[t.name] = {
7427
+ on: toTriggerCondition(t.on),
7428
+ then: toTriggerAction(t.then, opts.graphNodeIds),
7429
+ ...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
7430
+ };
7431
+ }
7432
+ }
7433
+ const handoff = schema.handoff?.fallback ? {
7434
+ fallback: schema.handoff.fallback,
7435
+ ...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
7436
+ } : void 0;
7437
+ const memory = schema.memory ? {
7438
+ ...schema.memory.scope ? { scope: schema.memory.scope } : {},
7439
+ ...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
7440
+ ...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
7441
+ } : void 0;
7442
+ let tools;
7443
+ if (schema.tools && schema.tools.length > 0) {
7444
+ tools = {};
7445
+ for (const t of schema.tools) {
7446
+ if (t.auth === "connection") {
7447
+ tools[t.name] = {
7448
+ description: t.description,
7449
+ input: t.input,
7450
+ run: () => ({
7451
+ kind: "unavailable",
7452
+ reason: "connection-auth tools need the vault resolver (not wired yet)"
7453
+ })
7454
+ };
7455
+ continue;
7456
+ }
7457
+ Object.assign(
7458
+ tools,
7459
+ httpTool({
7460
+ name: t.name,
7461
+ description: t.description,
7462
+ input: t.input,
7463
+ url: t.url,
7464
+ method: t.method,
7465
+ ...t.headers ? { headers: t.headers } : {}
7466
+ })
7467
+ );
7468
+ }
7469
+ }
7470
+ let speech;
7471
+ if (schema.speech) {
7472
+ const i = schema.speech.interruption;
7473
+ const e = schema.speech.endpointing;
7474
+ const interruption = i ? {
7475
+ ...i.enabled !== void 0 ? { enabled: i.enabled } : {},
7476
+ ...i.minWords !== void 0 ? { minWords: i.minWords } : {},
7477
+ ...i.minDuration !== void 0 ? { minDuration: i.minDuration } : {}
7478
+ } : void 0;
7479
+ const endpointing = e ? {
7480
+ ...e.minDelay !== void 0 ? { minDelay: e.minDelay } : {},
7481
+ ...e.maxDelay !== void 0 ? { maxDelay: e.maxDelay } : {}
7482
+ } : void 0;
7483
+ speech = {
7484
+ ...interruption && Object.keys(interruption).length ? { interruption } : {},
7485
+ ...endpointing && Object.keys(endpointing).length ? { endpointing } : {}
7486
+ };
7487
+ if (Object.keys(speech).length === 0) speech = void 0;
7488
+ }
7489
+ return {
7490
+ prompt,
7491
+ process: process2,
7492
+ ...triggers ? { triggers } : {},
7493
+ ...handoff ? { handoff } : {},
7494
+ ...memory ? { memory } : {},
7495
+ ...tools ? { tools } : {},
7496
+ ...speech ? { speech } : {}
7497
+ };
7498
+ }
7499
+
7368
7500
  // src/runtime/text-session.ts
7369
7501
  function buildTextContext(adapter, call, processRt) {
7370
7502
  return {
@@ -7382,6 +7514,18 @@ function buildTextContext(adapter, call, processRt) {
7382
7514
  get process() {
7383
7515
  return { data: processRt.getData(), complete: processRt.isComplete() };
7384
7516
  },
7517
+ // Voice-only actions, stubbed so a flow containing them can still be dry-run
7518
+ // over text. Without these the handoff node threw "deps.ctx.handoff is not
7519
+ // a function" mid-trace (seen in the builder's dry run); the graph now
7520
+ // reaches its handoff outcome cleanly and the trace says why nothing dialed.
7521
+ handoff: async (to) => {
7522
+ processRt.recordEvent({
7523
+ kind: "handoff.simulated",
7524
+ data: { to, reason: "text dry run \u2014 no telephony leg to transfer" }
7525
+ });
7526
+ },
7527
+ endCall: async () => {
7528
+ },
7385
7529
  // Voice-only escape hatch is absent on text; left undefined.
7386
7530
  livekit: { session: {}, job: {} }
7387
7531
  };
@@ -7423,14 +7567,51 @@ function runTextSession(opts) {
7423
7567
  end: () => handle.close()
7424
7568
  };
7425
7569
  }
7570
+ function deriveCollectFromProgram(program) {
7571
+ const collect = {};
7572
+ for (const [nodeId, node] of Object.entries(program.nodes)) {
7573
+ const cfg = node.config;
7574
+ if (node.type === "ask") {
7575
+ const name = typeof cfg.slot === "string" && cfg.slot ? cfg.slot : nodeId;
7576
+ const type = cfg.type;
7577
+ collect[name] = {
7578
+ type: type === "number" || type === "boolean" || type === "datetime" || type === "string[]" ? type : "string",
7579
+ ...cfg.required !== false ? { required: true } : {},
7580
+ ...typeof cfg.ask === "string" ? { ask: cfg.ask } : {},
7581
+ ...typeof cfg.pattern === "string" ? { pattern: new RegExp(cfg.pattern) } : {}
7582
+ };
7583
+ }
7584
+ if (cfg.outputAs && typeof cfg.outputAs === "object") {
7585
+ for (const slot of Object.keys(cfg.outputAs)) {
7586
+ collect[slot] ??= { type: "string" };
7587
+ }
7588
+ }
7589
+ if (Array.isArray(cfg.assignments)) {
7590
+ for (const a of cfg.assignments) {
7591
+ if (a && typeof a.name === "string" && a.name.trim()) collect[a.name.trim()] ??= { type: "string" };
7592
+ }
7593
+ }
7594
+ }
7595
+ return { collect, completeWhen: "all-required-captured" };
7596
+ }
7597
+ function definitionFor(opts) {
7598
+ if (opts.schema) {
7599
+ try {
7600
+ return processSchemaToDefinition(opts.schema);
7601
+ } catch {
7602
+ }
7603
+ }
7604
+ return deriveCollectFromProgram(opts.program);
7605
+ }
7426
7606
  async function runTextTranscript(opts) {
7427
7607
  const replies = [];
7428
7608
  const trace7 = [];
7609
+ const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
7429
7610
  const session = runTextSession({
7430
7611
  program: opts.program,
7431
7612
  onAgentText: (t) => void replies.push(t),
7432
7613
  onTrace: (e) => void trace7.push(e),
7433
- ...opts.processRt ? { processRt: opts.processRt } : {},
7614
+ ...processRt ? { processRt } : {},
7434
7615
  ...opts.generate ? { generate: opts.generate } : {},
7435
7616
  ...opts.call ? { call: opts.call } : {},
7436
7617
  ...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
@@ -8190,125 +8371,6 @@ function computeEmptyRequired(view) {
8190
8371
  }
8191
8372
  return empty;
8192
8373
  }
8193
-
8194
- // src/flow-runtime.ts
8195
- init_define_tool();
8196
- function processSchemaToDefinition(schema) {
8197
- const required = new Set(schema.completionGate.requiredFields);
8198
- const collect = {};
8199
- for (const f of schema.fields) {
8200
- collect[f.name] = {
8201
- type: f.type,
8202
- required: required.has(f.name) || f.required,
8203
- ...f.ask !== void 0 ? { ask: f.ask } : {},
8204
- ...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
8205
- ...f.min !== void 0 ? { min: f.min } : {},
8206
- ...f.max !== void 0 ? { max: f.max } : {},
8207
- ...f.enum !== void 0 ? { enum: f.enum } : {}
8208
- };
8209
- }
8210
- return {
8211
- collect,
8212
- completeWhen: "all-required-captured",
8213
- ...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
8214
- };
8215
- }
8216
- var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
8217
- function toTriggerCondition(on) {
8218
- return on.startsWith("signal:") ? on : new RegExp(on, "i");
8219
- }
8220
- function toTriggerAction(then, graphNodeIds) {
8221
- if (then === "endCall" || then === "end") return "endCall";
8222
- if (then === "handoff") return "handoff";
8223
- if (then.startsWith("say:")) return { say: then.slice(4).trim() };
8224
- if (graphNodeIds?.has(then)) return { goto: then };
8225
- return "handoff";
8226
- }
8227
- function processSchemaToAgentParts(schema, opts = {}) {
8228
- const process2 = processSchemaToDefinition(schema);
8229
- const promptParts = [];
8230
- if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
8231
- for (const p of schema.prompts ?? []) {
8232
- if (p.text.trim()) promptParts.push(p.text.trim());
8233
- }
8234
- const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
8235
- let triggers;
8236
- if (schema.triggers && schema.triggers.length > 0) {
8237
- triggers = {};
8238
- for (const t of schema.triggers) {
8239
- triggers[t.name] = {
8240
- on: toTriggerCondition(t.on),
8241
- then: toTriggerAction(t.then, opts.graphNodeIds),
8242
- ...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
8243
- };
8244
- }
8245
- }
8246
- const handoff = schema.handoff?.fallback ? {
8247
- fallback: schema.handoff.fallback,
8248
- ...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
8249
- } : void 0;
8250
- const memory = schema.memory ? {
8251
- ...schema.memory.scope ? { scope: schema.memory.scope } : {},
8252
- ...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
8253
- ...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
8254
- } : void 0;
8255
- let tools;
8256
- if (schema.tools && schema.tools.length > 0) {
8257
- tools = {};
8258
- for (const t of schema.tools) {
8259
- if (t.auth === "connection") {
8260
- tools[t.name] = {
8261
- description: t.description,
8262
- input: t.input,
8263
- run: () => ({
8264
- kind: "unavailable",
8265
- reason: "connection-auth tools need the vault resolver (not wired yet)"
8266
- })
8267
- };
8268
- continue;
8269
- }
8270
- Object.assign(
8271
- tools,
8272
- httpTool({
8273
- name: t.name,
8274
- description: t.description,
8275
- input: t.input,
8276
- url: t.url,
8277
- method: t.method,
8278
- ...t.headers ? { headers: t.headers } : {}
8279
- })
8280
- );
8281
- }
8282
- }
8283
- let speech;
8284
- if (schema.speech) {
8285
- const i = schema.speech.interruption;
8286
- const e = schema.speech.endpointing;
8287
- const interruption = i ? {
8288
- ...i.enabled !== void 0 ? { enabled: i.enabled } : {},
8289
- ...i.minWords !== void 0 ? { minWords: i.minWords } : {},
8290
- ...i.minDuration !== void 0 ? { minDuration: i.minDuration } : {}
8291
- } : void 0;
8292
- const endpointing = e ? {
8293
- ...e.minDelay !== void 0 ? { minDelay: e.minDelay } : {},
8294
- ...e.maxDelay !== void 0 ? { maxDelay: e.maxDelay } : {}
8295
- } : void 0;
8296
- speech = {
8297
- ...interruption && Object.keys(interruption).length ? { interruption } : {},
8298
- ...endpointing && Object.keys(endpointing).length ? { endpointing } : {}
8299
- };
8300
- if (Object.keys(speech).length === 0) speech = void 0;
8301
- }
8302
- return {
8303
- prompt,
8304
- process: process2,
8305
- ...triggers ? { triggers } : {},
8306
- ...handoff ? { handoff } : {},
8307
- ...memory ? { memory } : {},
8308
- ...tools ? { tools } : {},
8309
- ...speech ? { speech } : {}
8310
- };
8311
- }
8312
8374
  var BrainConfigError = class extends Error {
8313
8375
  constructor(message) {
8314
8376
  super(message);
@@ -10987,7 +11049,7 @@ async function executeHandoff(args, config, deps) {
10987
11049
  });
10988
11050
  }
10989
11051
  }
10990
- if (sipOps) {
11052
+ if (sipOps && isSipParticipant(deps.callerIdentity)) {
10991
11053
  if (mode === "warm") {
10992
11054
  await sayFixed(deps, "I'm transferring you now \u2014 one moment.");
10993
11055
  }
@@ -11003,6 +11065,19 @@ async function executeHandoff(args, config, deps) {
11003
11065
  error: err instanceof Error ? err.message : String(err)
11004
11066
  });
11005
11067
  }
11068
+ } else if (sipOps) {
11069
+ console.warn("[handoff] caller has no SIP leg; cannot transfer this channel", {
11070
+ callerIdentity: deps.callerIdentity,
11071
+ conferenceAvailable: sipOps.canConference
11072
+ });
11073
+ notify("handoff.unavailable", {
11074
+ to: args.to,
11075
+ reason: sipOps.canConference ? "caller has no SIP leg (web/playground call)" : "caller has no SIP leg and no outbound trunk is configured for conferencing"
11076
+ });
11077
+ await sayFixed(
11078
+ deps,
11079
+ "I'm not able to connect you to a person on this channel \u2014 please call our phone line and we'll pick up right where we left off."
11080
+ );
11006
11081
  }
11007
11082
  try {
11008
11083
  await deps.disconnect();
@@ -11052,6 +11127,9 @@ function buildDefaultSipOps() {
11052
11127
  }
11053
11128
  };
11054
11129
  }
11130
+ function isSipParticipant(identity) {
11131
+ return !/^(user[-_]|web[-_]|playground[-_]|vl-client)/i.test(identity.trim());
11132
+ }
11055
11133
  function normalizeSipTarget(target) {
11056
11134
  if (target.startsWith("sip:") || target.startsWith("tel:")) return target;
11057
11135
  return `tel:${target}`;
@@ -1,3 +1,3 @@
1
- export { a6 as GraphTraceEvent, q as RunTextSessionOptions, r as RunTextTranscriptOptions, T as TextSession, O as TextTranscriptResult, a3 as runTextSession, a4 as runTextTranscript } from '../text-session-BoUmDEX9.js';
1
+ export { aa as GraphTraceEvent, u as RunTextSessionOptions, v as RunTextTranscriptOptions, V as TextSession, W as TextTranscriptResult, a7 as runTextSession, a8 as runTextTranscript } from '../text-session-D1j8nQac.js';
2
2
  import '@livekit/agents';
3
3
  import 'zod';