@voicelayer/sdk 0.1.7 → 0.1.9
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.d.ts +25 -1002
- package/dist/index.js +302 -122
- package/dist/runtime/text-session.d.ts +1 -1
- package/dist/runtime/text-session.js +753 -17
- package/dist/text-session-D1j8nQac.d.ts +1996 -0
- package/package.json +5 -5
- package/dist/text-session-B8-jhQzv.d.ts +0 -837
package/dist/index.js
CHANGED
|
@@ -137,6 +137,9 @@ var init_flow = __esm({
|
|
|
137
137
|
"llm",
|
|
138
138
|
"rag",
|
|
139
139
|
"memory",
|
|
140
|
+
// Call-wide voice/turn-taking capability (barge-in + endpointing tuning);
|
|
141
|
+
// managed in the Capabilities panel like `memory`, never a graph step.
|
|
142
|
+
"speech",
|
|
140
143
|
"classify",
|
|
141
144
|
// Agentic router: per-turn, selects 1-of-N skills by their plain-language
|
|
142
145
|
// descriptions and takes that port (the resident routing surface / hub).
|
|
@@ -335,6 +338,19 @@ var init_agents = __esm({
|
|
|
335
338
|
}).optional(),
|
|
336
339
|
// Tool capabilities compiled from tool nodes (maps to AgentConfig.tools).
|
|
337
340
|
tools: z.array(ProcessToolDTO).max(32).optional(),
|
|
341
|
+
// Voice turn-taking compiled from the flow's speech capability node (maps to
|
|
342
|
+
// AgentConfig.speech → applySpeechTurnHandling). Absent = platform defaults.
|
|
343
|
+
speech: z.object({
|
|
344
|
+
interruption: z.object({
|
|
345
|
+
enabled: z.boolean().optional(),
|
|
346
|
+
minWords: z.number().int().min(0).max(10).optional(),
|
|
347
|
+
minDuration: z.number().int().min(0).max(5e3).optional()
|
|
348
|
+
}).optional(),
|
|
349
|
+
endpointing: z.object({
|
|
350
|
+
minDelay: z.number().int().min(0).max(5e3).optional(),
|
|
351
|
+
maxDelay: z.number().int().min(0).max(15e3).optional()
|
|
352
|
+
}).optional()
|
|
353
|
+
}).optional(),
|
|
338
354
|
// Graph-preserving compiled program. When present and the flow-runtime graph
|
|
339
355
|
// mode is enabled, the worker executes this node-graph deterministically
|
|
340
356
|
// (the interpreter) instead of the flat goals+rails form above. Embedded here
|
|
@@ -494,7 +510,7 @@ var init_consultation = __esm({
|
|
|
494
510
|
});
|
|
495
511
|
}
|
|
496
512
|
});
|
|
497
|
-
var CALL_CONTROL_PUBSUB_CHANNEL, CallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand, CallControlCommand;
|
|
513
|
+
var CALL_CONTROL_PUBSUB_CHANNEL, CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand, CallControlCommand;
|
|
498
514
|
var init_call_events = __esm({
|
|
499
515
|
"../contracts/src/call-events.ts"() {
|
|
500
516
|
init_consultation();
|
|
@@ -520,6 +536,8 @@ var init_call_events = __esm({
|
|
|
520
536
|
"dtmf.received",
|
|
521
537
|
"dtmf.sent"
|
|
522
538
|
]);
|
|
539
|
+
OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
|
|
540
|
+
OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
|
|
523
541
|
z.object({
|
|
524
542
|
reason: z.string().optional(),
|
|
525
543
|
durationMs: z.number().int().min(0).optional(),
|
|
@@ -557,7 +575,8 @@ var init_call_events = __esm({
|
|
|
557
575
|
seq: z.number().int().min(0),
|
|
558
576
|
callId: z.string().uuid(),
|
|
559
577
|
projectId: z.string().min(1),
|
|
560
|
-
|
|
578
|
+
// Closed lifecycle kinds OR the namespaced operational vocabulary.
|
|
579
|
+
kind: z.union([CallEventKind, OpCallEventKind]),
|
|
561
580
|
payload: z.unknown(),
|
|
562
581
|
ts: z.string().datetime()
|
|
563
582
|
});
|
|
@@ -1914,7 +1933,20 @@ function nodeConfigFor(node) {
|
|
|
1914
1933
|
...typeof data.connectionRef === "string" ? { connectionRef: data.connectionRef } : {},
|
|
1915
1934
|
// Registry reference: run the project HTTP tool with this NAME via the
|
|
1916
1935
|
// server-side proxy instead of the inline url/method/auth above.
|
|
1917
|
-
...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {}
|
|
1936
|
+
...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {},
|
|
1937
|
+
// Promote result fields into NAMED slots on success:
|
|
1938
|
+
// { slotName: "data.path" }. Later nodes then read {{ slotName }}
|
|
1939
|
+
// directly instead of {{ tool.<nodeId>.data.path }} via a set node.
|
|
1940
|
+
...(() => {
|
|
1941
|
+
if (!data.outputAs || typeof data.outputAs !== "object") return {};
|
|
1942
|
+
const outputAs = {};
|
|
1943
|
+
for (const [slot, path] of Object.entries(data.outputAs)) {
|
|
1944
|
+
if (typeof path === "string" && path.trim() && /^[a-zA-Z_][\w.]*$/.test(slot)) {
|
|
1945
|
+
outputAs[slot] = path.trim();
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
return Object.keys(outputAs).length ? { outputAs } : {};
|
|
1949
|
+
})()
|
|
1918
1950
|
};
|
|
1919
1951
|
}
|
|
1920
1952
|
case "gate":
|
|
@@ -1927,7 +1959,10 @@ function nodeConfigFor(node) {
|
|
|
1927
1959
|
...typeof data.fallback === "string" ? { fallback: data.fallback } : {},
|
|
1928
1960
|
...typeof data.on === "string" ? { on: data.on } : {},
|
|
1929
1961
|
// Per-node warm (conference brief) / cold (blind REFER) override.
|
|
1930
|
-
...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {}
|
|
1962
|
+
...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {},
|
|
1963
|
+
// What the joining human HEARS (warm mode speaks it verbatim once they
|
|
1964
|
+
// answer). {{ }}-interpolated at runtime against the slot scope.
|
|
1965
|
+
...typeof data.briefing === "string" && data.briefing.trim() ? { briefing: data.briefing } : {}
|
|
1931
1966
|
};
|
|
1932
1967
|
case "end":
|
|
1933
1968
|
return {
|
|
@@ -2091,7 +2126,7 @@ var init_flow_compile = __esm({
|
|
|
2091
2126
|
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2092
2127
|
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2093
2128
|
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2094
|
-
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger"]);
|
|
2129
|
+
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2095
2130
|
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
2096
2131
|
}
|
|
2097
2132
|
});
|
|
@@ -3098,7 +3133,7 @@ var init_executors = __esm({
|
|
|
3098
3133
|
const authoredSlot = slot === node.id ? "" : slot.trim();
|
|
3099
3134
|
const label = authoredSlot.replace(/[_-]+/g, " ");
|
|
3100
3135
|
if (hasSlot(deps.processRt.getData(), slot)) {
|
|
3101
|
-
|
|
3136
|
+
deps.events.note("ask.skipped", { slot: slot.trim(), reason: "already known" });
|
|
3102
3137
|
return { kind: "advance" };
|
|
3103
3138
|
}
|
|
3104
3139
|
const askText = interpolate(
|
|
@@ -3112,10 +3147,10 @@ var init_executors = __esm({
|
|
|
3112
3147
|
state.lastUtterance = t.text;
|
|
3113
3148
|
state.lastDtmf = t.dtmf;
|
|
3114
3149
|
if (hasSlot(deps.processRt.getData(), slot)) {
|
|
3115
|
-
|
|
3150
|
+
deps.events.note("ask.captured", { slot: slot.trim() });
|
|
3116
3151
|
return { kind: "advance" };
|
|
3117
3152
|
}
|
|
3118
|
-
|
|
3153
|
+
deps.events.note("ask.clarify", { slot: slot.trim(), turn });
|
|
3119
3154
|
await withHistory(
|
|
3120
3155
|
deps,
|
|
3121
3156
|
historyPolicyOf(node),
|
|
@@ -3141,7 +3176,7 @@ var init_executors = __esm({
|
|
|
3141
3176
|
if (msg) await speak(interpolate(msg, scopeFor(state, deps)), deps);
|
|
3142
3177
|
return { kind: "end" };
|
|
3143
3178
|
};
|
|
3144
|
-
handoffExecutor = async (node,
|
|
3179
|
+
handoffExecutor = async (node, state, deps) => {
|
|
3145
3180
|
const cfg = node.config;
|
|
3146
3181
|
const target = str(cfg.to) || str(cfg.fallback);
|
|
3147
3182
|
if (!target) {
|
|
@@ -3150,9 +3185,14 @@ var init_executors = __esm({
|
|
|
3150
3185
|
}
|
|
3151
3186
|
const reason = str(cfg.reason) || `flow:${node.id}`;
|
|
3152
3187
|
const mode = cfg.mode === "warm" || cfg.mode === "cold" ? cfg.mode : void 0;
|
|
3188
|
+
const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing), scopeFor(state, deps)) : void 0;
|
|
3153
3189
|
deps.events.note("handoff.start", { node: node.id, to: target, ...mode ? { mode } : {} });
|
|
3154
3190
|
try {
|
|
3155
|
-
await deps.ctx.handoff(target, {
|
|
3191
|
+
await deps.ctx.handoff(target, {
|
|
3192
|
+
reason,
|
|
3193
|
+
...mode ? { mode } : {},
|
|
3194
|
+
...briefing ? { briefing } : {}
|
|
3195
|
+
});
|
|
3156
3196
|
} catch (err) {
|
|
3157
3197
|
deps.events.note("handoff.failed", {
|
|
3158
3198
|
node: node.id,
|
|
@@ -3206,7 +3246,24 @@ ${collected}`);
|
|
|
3206
3246
|
const cfg = node.config;
|
|
3207
3247
|
const name = str(cfg.name) || node.id;
|
|
3208
3248
|
const routeByOutcome = () => {
|
|
3209
|
-
const
|
|
3249
|
+
const result = state.toolResults[node.id];
|
|
3250
|
+
const ok = result?.ok === true;
|
|
3251
|
+
if (ok && cfg.outputAs && typeof cfg.outputAs === "object") {
|
|
3252
|
+
for (const [slot, path] of Object.entries(cfg.outputAs)) {
|
|
3253
|
+
if (typeof path !== "string" || !path.trim()) continue;
|
|
3254
|
+
const value = resolvePath(result, path.trim());
|
|
3255
|
+
if (value === void 0 || value === null) {
|
|
3256
|
+
deps.events.note("tool.output_missing", { node: node.id, slot, path });
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
const captured = deps.processRt.captureField(slot, value);
|
|
3260
|
+
deps.events.note(captured ? "tool.output_captured" : "tool.output_rejected", {
|
|
3261
|
+
node: node.id,
|
|
3262
|
+
slot,
|
|
3263
|
+
...captured ? {} : { reason: "slot not declared or value not coercible" }
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3210
3267
|
return routeByPort(node, deps, ok ? "ok" : "fail");
|
|
3211
3268
|
};
|
|
3212
3269
|
const toolRef = str(cfg.toolRef);
|
|
@@ -3713,13 +3770,19 @@ ${collected}`);
|
|
|
3713
3770
|
setExecutor = async (node, state, deps) => {
|
|
3714
3771
|
const assignments = Array.isArray(node.config.assignments) ? node.config.assignments : [];
|
|
3715
3772
|
let applied = 0;
|
|
3773
|
+
const rejected = [];
|
|
3716
3774
|
for (const a of assignments) {
|
|
3717
3775
|
if (!a || typeof a.name !== "string" || !a.name.trim()) continue;
|
|
3776
|
+
const name = a.name.trim();
|
|
3718
3777
|
const value = interpolate(typeof a.value === "string" ? a.value : "", scopeFor(state, deps));
|
|
3719
|
-
deps.processRt.captureField(
|
|
3720
|
-
|
|
3778
|
+
if (deps.processRt.captureField(name, value)) applied += 1;
|
|
3779
|
+
else rejected.push(name);
|
|
3721
3780
|
}
|
|
3722
|
-
deps.events.note("set.applied", {
|
|
3781
|
+
deps.events.note("set.applied", {
|
|
3782
|
+
node: node.id,
|
|
3783
|
+
count: applied,
|
|
3784
|
+
...rejected.length ? { rejected } : {}
|
|
3785
|
+
});
|
|
3723
3786
|
return { kind: "advance" };
|
|
3724
3787
|
};
|
|
3725
3788
|
MAX_SUBFLOW_DEPTH = 3;
|
|
@@ -5262,12 +5325,15 @@ var CallsClient = class {
|
|
|
5262
5325
|
);
|
|
5263
5326
|
}
|
|
5264
5327
|
/**
|
|
5265
|
-
* Append a structured call event
|
|
5266
|
-
*
|
|
5328
|
+
* Append a structured call event — consultation lifecycle kinds, or the
|
|
5329
|
+
* operational vocabulary the call-detail Timeline renders between
|
|
5330
|
+
* transcript rows (tool.* / handoff.* / lookup.* / record.* / notify.*).
|
|
5331
|
+
* See apps/api/src/routes/calls.ts AppendEventBody for the accepted shapes.
|
|
5267
5332
|
*
|
|
5268
5333
|
* The agent SDK's built-in `ask_host` tool calls this on consultation start
|
|
5269
|
-
* / end
|
|
5270
|
-
*
|
|
5334
|
+
* / end; the handoff runtime and graph interpreter emit op events through it
|
|
5335
|
+
* so the operational story (dial → answered → briefed → dropped, tool calls)
|
|
5336
|
+
* is visible in the dashboard, not just the spoken transcript.
|
|
5271
5337
|
*/
|
|
5272
5338
|
async appendEvent(callId, input) {
|
|
5273
5339
|
await this.transport.request(
|
|
@@ -5686,6 +5752,18 @@ init_conditions();
|
|
|
5686
5752
|
init_interpreter();
|
|
5687
5753
|
|
|
5688
5754
|
// src/runtime/graph/events.ts
|
|
5755
|
+
var OP_EVENT_NOTE = /^(tool|handoff|lookup|record|notify)\./;
|
|
5756
|
+
function withOpEventFanout(events, notify) {
|
|
5757
|
+
return {
|
|
5758
|
+
enter: (nodeId, type) => events.enter(nodeId, type),
|
|
5759
|
+
traverse: (from, to, via) => events.traverse(from, to, via),
|
|
5760
|
+
note(kind, data) {
|
|
5761
|
+
events.note(kind, data);
|
|
5762
|
+
if (OP_EVENT_NOTE.test(kind)) notify(kind, data ?? {});
|
|
5763
|
+
},
|
|
5764
|
+
currentNodeId: () => events.currentNodeId()
|
|
5765
|
+
};
|
|
5766
|
+
}
|
|
5689
5767
|
function createGraphEvents(processRt) {
|
|
5690
5768
|
let current = null;
|
|
5691
5769
|
return {
|
|
@@ -7299,6 +7377,125 @@ function createTextSessionAdapter(transport) {
|
|
|
7299
7377
|
return { adapter, push, setState, close };
|
|
7300
7378
|
}
|
|
7301
7379
|
|
|
7380
|
+
// src/flow-runtime.ts
|
|
7381
|
+
init_define_tool();
|
|
7382
|
+
function processSchemaToDefinition(schema) {
|
|
7383
|
+
const required = new Set(schema.completionGate.requiredFields);
|
|
7384
|
+
const collect = {};
|
|
7385
|
+
for (const f of schema.fields) {
|
|
7386
|
+
collect[f.name] = {
|
|
7387
|
+
type: f.type,
|
|
7388
|
+
required: required.has(f.name) || f.required,
|
|
7389
|
+
...f.ask !== void 0 ? { ask: f.ask } : {},
|
|
7390
|
+
...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
|
|
7391
|
+
...f.min !== void 0 ? { min: f.min } : {},
|
|
7392
|
+
...f.max !== void 0 ? { max: f.max } : {},
|
|
7393
|
+
...f.enum !== void 0 ? { enum: f.enum } : {}
|
|
7394
|
+
};
|
|
7395
|
+
}
|
|
7396
|
+
return {
|
|
7397
|
+
collect,
|
|
7398
|
+
completeWhen: "all-required-captured",
|
|
7399
|
+
...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
|
|
7400
|
+
};
|
|
7401
|
+
}
|
|
7402
|
+
var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
|
|
7403
|
+
function toTriggerCondition(on) {
|
|
7404
|
+
return on.startsWith("signal:") ? on : new RegExp(on, "i");
|
|
7405
|
+
}
|
|
7406
|
+
function toTriggerAction(then, graphNodeIds) {
|
|
7407
|
+
if (then === "endCall" || then === "end") return "endCall";
|
|
7408
|
+
if (then === "handoff") return "handoff";
|
|
7409
|
+
if (then.startsWith("say:")) return { say: then.slice(4).trim() };
|
|
7410
|
+
if (graphNodeIds?.has(then)) return { goto: then };
|
|
7411
|
+
return "handoff";
|
|
7412
|
+
}
|
|
7413
|
+
function processSchemaToAgentParts(schema, opts = {}) {
|
|
7414
|
+
const process2 = processSchemaToDefinition(schema);
|
|
7415
|
+
const promptParts = [];
|
|
7416
|
+
if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
|
|
7417
|
+
for (const p of schema.prompts ?? []) {
|
|
7418
|
+
if (p.text.trim()) promptParts.push(p.text.trim());
|
|
7419
|
+
}
|
|
7420
|
+
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
|
|
7421
|
+
let triggers;
|
|
7422
|
+
if (schema.triggers && schema.triggers.length > 0) {
|
|
7423
|
+
triggers = {};
|
|
7424
|
+
for (const t of schema.triggers) {
|
|
7425
|
+
triggers[t.name] = {
|
|
7426
|
+
on: toTriggerCondition(t.on),
|
|
7427
|
+
then: toTriggerAction(t.then, opts.graphNodeIds),
|
|
7428
|
+
...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
|
|
7429
|
+
};
|
|
7430
|
+
}
|
|
7431
|
+
}
|
|
7432
|
+
const handoff = schema.handoff?.fallback ? {
|
|
7433
|
+
fallback: schema.handoff.fallback,
|
|
7434
|
+
...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
|
|
7435
|
+
} : void 0;
|
|
7436
|
+
const memory = schema.memory ? {
|
|
7437
|
+
...schema.memory.scope ? { scope: schema.memory.scope } : {},
|
|
7438
|
+
...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
|
|
7439
|
+
...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
|
|
7440
|
+
} : void 0;
|
|
7441
|
+
let tools;
|
|
7442
|
+
if (schema.tools && schema.tools.length > 0) {
|
|
7443
|
+
tools = {};
|
|
7444
|
+
for (const t of schema.tools) {
|
|
7445
|
+
if (t.auth === "connection") {
|
|
7446
|
+
tools[t.name] = {
|
|
7447
|
+
description: t.description,
|
|
7448
|
+
input: t.input,
|
|
7449
|
+
run: () => ({
|
|
7450
|
+
kind: "unavailable",
|
|
7451
|
+
reason: "connection-auth tools need the vault resolver (not wired yet)"
|
|
7452
|
+
})
|
|
7453
|
+
};
|
|
7454
|
+
continue;
|
|
7455
|
+
}
|
|
7456
|
+
Object.assign(
|
|
7457
|
+
tools,
|
|
7458
|
+
httpTool({
|
|
7459
|
+
name: t.name,
|
|
7460
|
+
description: t.description,
|
|
7461
|
+
input: t.input,
|
|
7462
|
+
url: t.url,
|
|
7463
|
+
method: t.method,
|
|
7464
|
+
...t.headers ? { headers: t.headers } : {}
|
|
7465
|
+
})
|
|
7466
|
+
);
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
let speech;
|
|
7470
|
+
if (schema.speech) {
|
|
7471
|
+
const i = schema.speech.interruption;
|
|
7472
|
+
const e = schema.speech.endpointing;
|
|
7473
|
+
const interruption = i ? {
|
|
7474
|
+
...i.enabled !== void 0 ? { enabled: i.enabled } : {},
|
|
7475
|
+
...i.minWords !== void 0 ? { minWords: i.minWords } : {},
|
|
7476
|
+
...i.minDuration !== void 0 ? { minDuration: i.minDuration } : {}
|
|
7477
|
+
} : void 0;
|
|
7478
|
+
const endpointing = e ? {
|
|
7479
|
+
...e.minDelay !== void 0 ? { minDelay: e.minDelay } : {},
|
|
7480
|
+
...e.maxDelay !== void 0 ? { maxDelay: e.maxDelay } : {}
|
|
7481
|
+
} : void 0;
|
|
7482
|
+
speech = {
|
|
7483
|
+
...interruption && Object.keys(interruption).length ? { interruption } : {},
|
|
7484
|
+
...endpointing && Object.keys(endpointing).length ? { endpointing } : {}
|
|
7485
|
+
};
|
|
7486
|
+
if (Object.keys(speech).length === 0) speech = void 0;
|
|
7487
|
+
}
|
|
7488
|
+
return {
|
|
7489
|
+
prompt,
|
|
7490
|
+
process: process2,
|
|
7491
|
+
...triggers ? { triggers } : {},
|
|
7492
|
+
...handoff ? { handoff } : {},
|
|
7493
|
+
...memory ? { memory } : {},
|
|
7494
|
+
...tools ? { tools } : {},
|
|
7495
|
+
...speech ? { speech } : {}
|
|
7496
|
+
};
|
|
7497
|
+
}
|
|
7498
|
+
|
|
7302
7499
|
// src/runtime/text-session.ts
|
|
7303
7500
|
function buildTextContext(adapter, call, processRt) {
|
|
7304
7501
|
return {
|
|
@@ -7357,14 +7554,51 @@ function runTextSession(opts) {
|
|
|
7357
7554
|
end: () => handle.close()
|
|
7358
7555
|
};
|
|
7359
7556
|
}
|
|
7557
|
+
function deriveCollectFromProgram(program) {
|
|
7558
|
+
const collect = {};
|
|
7559
|
+
for (const [nodeId, node] of Object.entries(program.nodes)) {
|
|
7560
|
+
const cfg = node.config;
|
|
7561
|
+
if (node.type === "ask") {
|
|
7562
|
+
const name = typeof cfg.slot === "string" && cfg.slot ? cfg.slot : nodeId;
|
|
7563
|
+
const type = cfg.type;
|
|
7564
|
+
collect[name] = {
|
|
7565
|
+
type: type === "number" || type === "boolean" || type === "datetime" || type === "string[]" ? type : "string",
|
|
7566
|
+
...cfg.required !== false ? { required: true } : {},
|
|
7567
|
+
...typeof cfg.ask === "string" ? { ask: cfg.ask } : {},
|
|
7568
|
+
...typeof cfg.pattern === "string" ? { pattern: new RegExp(cfg.pattern) } : {}
|
|
7569
|
+
};
|
|
7570
|
+
}
|
|
7571
|
+
if (cfg.outputAs && typeof cfg.outputAs === "object") {
|
|
7572
|
+
for (const slot of Object.keys(cfg.outputAs)) {
|
|
7573
|
+
collect[slot] ??= { type: "string" };
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
if (Array.isArray(cfg.assignments)) {
|
|
7577
|
+
for (const a of cfg.assignments) {
|
|
7578
|
+
if (a && typeof a.name === "string" && a.name.trim()) collect[a.name.trim()] ??= { type: "string" };
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
return { collect, completeWhen: "all-required-captured" };
|
|
7583
|
+
}
|
|
7584
|
+
function definitionFor(opts) {
|
|
7585
|
+
if (opts.schema) {
|
|
7586
|
+
try {
|
|
7587
|
+
return processSchemaToDefinition(opts.schema);
|
|
7588
|
+
} catch {
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
return deriveCollectFromProgram(opts.program);
|
|
7592
|
+
}
|
|
7360
7593
|
async function runTextTranscript(opts) {
|
|
7361
7594
|
const replies = [];
|
|
7362
7595
|
const trace7 = [];
|
|
7596
|
+
const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
|
|
7363
7597
|
const session = runTextSession({
|
|
7364
7598
|
program: opts.program,
|
|
7365
7599
|
onAgentText: (t) => void replies.push(t),
|
|
7366
7600
|
onTrace: (e) => void trace7.push(e),
|
|
7367
|
-
...
|
|
7601
|
+
...processRt ? { processRt } : {},
|
|
7368
7602
|
...opts.generate ? { generate: opts.generate } : {},
|
|
7369
7603
|
...opts.call ? { call: opts.call } : {},
|
|
7370
7604
|
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
|
|
@@ -8124,105 +8358,6 @@ function computeEmptyRequired(view) {
|
|
|
8124
8358
|
}
|
|
8125
8359
|
return empty;
|
|
8126
8360
|
}
|
|
8127
|
-
|
|
8128
|
-
// src/flow-runtime.ts
|
|
8129
|
-
init_define_tool();
|
|
8130
|
-
function processSchemaToDefinition(schema) {
|
|
8131
|
-
const required = new Set(schema.completionGate.requiredFields);
|
|
8132
|
-
const collect = {};
|
|
8133
|
-
for (const f of schema.fields) {
|
|
8134
|
-
collect[f.name] = {
|
|
8135
|
-
type: f.type,
|
|
8136
|
-
required: required.has(f.name) || f.required,
|
|
8137
|
-
...f.ask !== void 0 ? { ask: f.ask } : {},
|
|
8138
|
-
...f.pattern !== void 0 ? { pattern: new RegExp(f.pattern) } : {},
|
|
8139
|
-
...f.min !== void 0 ? { min: f.min } : {},
|
|
8140
|
-
...f.max !== void 0 ? { max: f.max } : {},
|
|
8141
|
-
...f.enum !== void 0 ? { enum: f.enum } : {}
|
|
8142
|
-
};
|
|
8143
|
-
}
|
|
8144
|
-
return {
|
|
8145
|
-
collect,
|
|
8146
|
-
completeWhen: "all-required-captured",
|
|
8147
|
-
...schema.completionGate.backendAck !== void 0 ? { backendAck: schema.completionGate.backendAck } : {}
|
|
8148
|
-
};
|
|
8149
|
-
}
|
|
8150
|
-
var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
|
|
8151
|
-
function toTriggerCondition(on) {
|
|
8152
|
-
return on.startsWith("signal:") ? on : new RegExp(on, "i");
|
|
8153
|
-
}
|
|
8154
|
-
function toTriggerAction(then, graphNodeIds) {
|
|
8155
|
-
if (then === "endCall" || then === "end") return "endCall";
|
|
8156
|
-
if (then === "handoff") return "handoff";
|
|
8157
|
-
if (then.startsWith("say:")) return { say: then.slice(4).trim() };
|
|
8158
|
-
if (graphNodeIds?.has(then)) return { goto: then };
|
|
8159
|
-
return "handoff";
|
|
8160
|
-
}
|
|
8161
|
-
function processSchemaToAgentParts(schema, opts = {}) {
|
|
8162
|
-
const process2 = processSchemaToDefinition(schema);
|
|
8163
|
-
const promptParts = [];
|
|
8164
|
-
if (opts.basePrompt && opts.basePrompt.trim()) promptParts.push(opts.basePrompt.trim());
|
|
8165
|
-
for (const p of schema.prompts ?? []) {
|
|
8166
|
-
if (p.text.trim()) promptParts.push(p.text.trim());
|
|
8167
|
-
}
|
|
8168
|
-
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PROMPT;
|
|
8169
|
-
let triggers;
|
|
8170
|
-
if (schema.triggers && schema.triggers.length > 0) {
|
|
8171
|
-
triggers = {};
|
|
8172
|
-
for (const t of schema.triggers) {
|
|
8173
|
-
triggers[t.name] = {
|
|
8174
|
-
on: toTriggerCondition(t.on),
|
|
8175
|
-
then: toTriggerAction(t.then, opts.graphNodeIds),
|
|
8176
|
-
...t.onlyWhile ? { onlyWhile: t.onlyWhile } : {}
|
|
8177
|
-
};
|
|
8178
|
-
}
|
|
8179
|
-
}
|
|
8180
|
-
const handoff = schema.handoff?.fallback ? {
|
|
8181
|
-
fallback: schema.handoff.fallback,
|
|
8182
|
-
...schema.handoff.routes ? { routes: schema.handoff.routes } : {}
|
|
8183
|
-
} : void 0;
|
|
8184
|
-
const memory = schema.memory ? {
|
|
8185
|
-
...schema.memory.scope ? { scope: schema.memory.scope } : {},
|
|
8186
|
-
...schema.memory.retentionDays !== void 0 ? { retentionDays: schema.memory.retentionDays } : {},
|
|
8187
|
-
...schema.memory.piiFields ? { piiFields: schema.memory.piiFields } : {}
|
|
8188
|
-
} : void 0;
|
|
8189
|
-
let tools;
|
|
8190
|
-
if (schema.tools && schema.tools.length > 0) {
|
|
8191
|
-
tools = {};
|
|
8192
|
-
for (const t of schema.tools) {
|
|
8193
|
-
if (t.auth === "connection") {
|
|
8194
|
-
tools[t.name] = {
|
|
8195
|
-
description: t.description,
|
|
8196
|
-
input: t.input,
|
|
8197
|
-
run: () => ({
|
|
8198
|
-
kind: "unavailable",
|
|
8199
|
-
reason: "connection-auth tools need the vault resolver (not wired yet)"
|
|
8200
|
-
})
|
|
8201
|
-
};
|
|
8202
|
-
continue;
|
|
8203
|
-
}
|
|
8204
|
-
Object.assign(
|
|
8205
|
-
tools,
|
|
8206
|
-
httpTool({
|
|
8207
|
-
name: t.name,
|
|
8208
|
-
description: t.description,
|
|
8209
|
-
input: t.input,
|
|
8210
|
-
url: t.url,
|
|
8211
|
-
method: t.method,
|
|
8212
|
-
...t.headers ? { headers: t.headers } : {}
|
|
8213
|
-
})
|
|
8214
|
-
);
|
|
8215
|
-
}
|
|
8216
|
-
}
|
|
8217
|
-
return {
|
|
8218
|
-
prompt,
|
|
8219
|
-
process: process2,
|
|
8220
|
-
...triggers ? { triggers } : {},
|
|
8221
|
-
...handoff ? { handoff } : {},
|
|
8222
|
-
...memory ? { memory } : {},
|
|
8223
|
-
...tools ? { tools } : {}
|
|
8224
|
-
};
|
|
8225
|
-
}
|
|
8226
8361
|
var BrainConfigError = class extends Error {
|
|
8227
8362
|
constructor(message) {
|
|
8228
8363
|
super(message);
|
|
@@ -10877,29 +11012,59 @@ async function executeHandoff(args, config, deps) {
|
|
|
10877
11012
|
const briefing = args.briefing ?? await buildBriefing(config, deps) ?? `Handoff to ${args.to}`;
|
|
10878
11013
|
const mode = args.mode ?? config?.mode ?? "warm";
|
|
10879
11014
|
const sipOps = deps.sipOps ?? buildDefaultSipOps();
|
|
11015
|
+
const notify = deps.notify ?? (() => {
|
|
11016
|
+
});
|
|
10880
11017
|
if (mode === "warm" && sipOps && sipOps.canConference) {
|
|
10881
11018
|
try {
|
|
10882
11019
|
await sayFixed(deps, "One moment \u2014 I'm bringing our on-call engineer onto the line now. Stay with me.");
|
|
11020
|
+
notify("handoff.dialing", { to: args.to, mode: "warm" });
|
|
10883
11021
|
await sipOps.addParticipant(deps.roomName, normalizeSipTarget(args.to));
|
|
11022
|
+
notify("handoff.answered", { to: args.to });
|
|
10884
11023
|
await sayFixed(deps, briefing);
|
|
11024
|
+
notify("handoff.briefed", { to: args.to, briefing });
|
|
10885
11025
|
await sayFixed(deps, "You're both connected now \u2014 I'll drop off.");
|
|
10886
11026
|
await quiesce(deps);
|
|
10887
11027
|
await deps.disconnect();
|
|
11028
|
+
notify("handoff.agent_dropped", { to: args.to });
|
|
10888
11029
|
return { ok: true, via: "conference", target: args.to };
|
|
10889
11030
|
} catch (err) {
|
|
10890
11031
|
console.error("[handoff] conference bridge failed, falling back to REFER", err);
|
|
11032
|
+
notify("handoff.failed", {
|
|
11033
|
+
to: args.to,
|
|
11034
|
+
via: "conference",
|
|
11035
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11036
|
+
});
|
|
10891
11037
|
}
|
|
10892
11038
|
}
|
|
10893
|
-
if (sipOps) {
|
|
11039
|
+
if (sipOps && isSipParticipant(deps.callerIdentity)) {
|
|
10894
11040
|
if (mode === "warm") {
|
|
10895
11041
|
await sayFixed(deps, "I'm transferring you now \u2014 one moment.");
|
|
10896
11042
|
}
|
|
10897
11043
|
try {
|
|
10898
11044
|
await sipOps.refer(deps.roomName, deps.callerIdentity, normalizeSipTarget(args.to));
|
|
11045
|
+
notify("handoff.refer", { to: args.to });
|
|
10899
11046
|
return { ok: true, via: "sip", target: args.to };
|
|
10900
11047
|
} catch (err) {
|
|
10901
11048
|
console.error("[handoff] SIP REFER failed, falling back to disconnect", err);
|
|
11049
|
+
notify("handoff.failed", {
|
|
11050
|
+
to: args.to,
|
|
11051
|
+
via: "refer",
|
|
11052
|
+
error: err instanceof Error ? err.message : String(err)
|
|
11053
|
+
});
|
|
10902
11054
|
}
|
|
11055
|
+
} else if (sipOps) {
|
|
11056
|
+
console.warn("[handoff] caller has no SIP leg; cannot transfer this channel", {
|
|
11057
|
+
callerIdentity: deps.callerIdentity,
|
|
11058
|
+
conferenceAvailable: sipOps.canConference
|
|
11059
|
+
});
|
|
11060
|
+
notify("handoff.unavailable", {
|
|
11061
|
+
to: args.to,
|
|
11062
|
+
reason: sipOps.canConference ? "caller has no SIP leg (web/playground call)" : "caller has no SIP leg and no outbound trunk is configured for conferencing"
|
|
11063
|
+
});
|
|
11064
|
+
await sayFixed(
|
|
11065
|
+
deps,
|
|
11066
|
+
"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."
|
|
11067
|
+
);
|
|
10903
11068
|
}
|
|
10904
11069
|
try {
|
|
10905
11070
|
await deps.disconnect();
|
|
@@ -10949,6 +11114,9 @@ function buildDefaultSipOps() {
|
|
|
10949
11114
|
}
|
|
10950
11115
|
};
|
|
10951
11116
|
}
|
|
11117
|
+
function isSipParticipant(identity) {
|
|
11118
|
+
return !/^(user[-_]|web[-_]|playground[-_]|vl-client)/i.test(identity.trim());
|
|
11119
|
+
}
|
|
10952
11120
|
function normalizeSipTarget(target) {
|
|
10953
11121
|
if (target.startsWith("sip:") || target.startsWith("tel:")) return target;
|
|
10954
11122
|
return `tel:${target}`;
|
|
@@ -12645,7 +12813,11 @@ var Agent = class {
|
|
|
12645
12813
|
...parts.triggers ? { triggers: parts.triggers } : {},
|
|
12646
12814
|
...parts.handoff ? { handoff: parts.handoff } : {},
|
|
12647
12815
|
...parts.memory ? { memory: parts.memory } : {},
|
|
12648
|
-
...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {}
|
|
12816
|
+
...parts.tools ? { tools: { ...effectiveConfig.tools ?? {}, ...parts.tools } } : {},
|
|
12817
|
+
// Flow-authored turn-taking (schema.speech). Merged over any
|
|
12818
|
+
// worker-level speech config; applySpeechTurnHandling reads this
|
|
12819
|
+
// off effectiveConfig at session build.
|
|
12820
|
+
...parts.speech ? { speech: { ...effectiveConfig.speech ?? {}, ...parts.speech } } : {}
|
|
12649
12821
|
};
|
|
12650
12822
|
console.log("[agent] booted from flow", {
|
|
12651
12823
|
agentId: flowAgentId,
|
|
@@ -12706,7 +12878,13 @@ var Agent = class {
|
|
|
12706
12878
|
const processRt = createProcessRuntime(effectiveConfig.process);
|
|
12707
12879
|
seedCallPrefill(processRt, dispatchMdEarly);
|
|
12708
12880
|
await seedProjectVariables(processRt);
|
|
12709
|
-
|
|
12881
|
+
let opEventCallId = null;
|
|
12882
|
+
const emitOpEvent = (kind, payload) => {
|
|
12883
|
+
if (!sdkClient || !opEventCallId) return;
|
|
12884
|
+
void sdkClient.calls.appendEvent(opEventCallId, { kind, payload }).catch(() => {
|
|
12885
|
+
});
|
|
12886
|
+
};
|
|
12887
|
+
const graphEvents = graphMode ? withOpEventFanout(createGraphEvents(processRt), emitOpEvent) : null;
|
|
12710
12888
|
const isOutbound = stringValue(dispatchMdEarly["direction"]) === "outbound";
|
|
12711
12889
|
const perCallAmd = stringValue(dispatchMdEarly["amd"]);
|
|
12712
12890
|
const amdMode = perCallAmd === "off" || perCallAmd === "detect" || perCallAmd === "detectMessageEnd" ? perCallAmd : effectiveConfig.outbound?.amd ?? "off";
|
|
@@ -12798,6 +12976,7 @@ var Agent = class {
|
|
|
12798
12976
|
});
|
|
12799
12977
|
}
|
|
12800
12978
|
}
|
|
12979
|
+
opEventCallId = consultationCallId;
|
|
12801
12980
|
let agentSpeechState = "initializing";
|
|
12802
12981
|
const idleWaiters = /* @__PURE__ */ new Set();
|
|
12803
12982
|
const waitUntilAgentIdle = async (timeoutMs) => {
|
|
@@ -12981,7 +13160,8 @@ ${callIntent}`
|
|
|
12981
13160
|
}
|
|
12982
13161
|
},
|
|
12983
13162
|
getProcessData: () => processRt.getData(),
|
|
12984
|
-
transcript
|
|
13163
|
+
transcript,
|
|
13164
|
+
notify: (kind, payload) => emitOpEvent(kind, payload)
|
|
12985
13165
|
});
|
|
12986
13166
|
const triggersRt = createTriggersRuntime(effectiveConfig.triggers, handoffRt);
|
|
12987
13167
|
const audioExperience = new AudioExperiencePrimitive();
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
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';
|