@absolutejs/voice 0.0.22-beta.513 → 0.0.22-beta.514
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 +10 -0
- package/dist/index.js +781 -0
- package/dist/pathway.d.ts +94 -0
- package/dist/pathwayCompiler.d.ts +31 -0
- package/dist/pathwayRuntime.d.ts +57 -0
- package/dist/pathwaySlotCollector.d.ts +29 -0
- package/dist/pathwayVisualizer.d.ts +8 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -341,4 +341,14 @@ export { computeVoiceScorecardCalibration } from "./scorecardCalibration";
|
|
|
341
341
|
export type { VoiceScorecardCalibrationDivergence, VoiceScorecardCalibrationPair, VoiceScorecardCalibrationReport, } from "./scorecardCalibration";
|
|
342
342
|
export { detectVoiceQualityDrift } from "./qualityDriftDetector";
|
|
343
343
|
export type { DetectVoiceQualityDriftInput, VoiceQualityDriftCriterionAlert, VoiceQualityDriftReport, VoiceQualityDriftSeverity, } from "./qualityDriftDetector";
|
|
344
|
+
export { findVoicePathwaySlot, findVoicePathwayState, validateVoicePathway, } from "./pathway";
|
|
345
|
+
export type { VoicePathway, VoicePathwayAction, VoicePathwayCondition, VoicePathwaySlot, VoicePathwaySlotType, VoicePathwayState, VoicePathwayTransition, VoicePathwayValidationIssue, VoicePathwayValidationReport, } from "./pathway";
|
|
346
|
+
export { createVoicePathwayRuntime } from "./pathwayRuntime";
|
|
347
|
+
export type { CreateVoicePathwayRuntimeOptions, VoicePathwayRuntime, VoicePathwayRuntimeEvent, VoicePathwayRuntimeState, VoicePathwayRuntimeStatus, VoicePathwaySlotValue, VoicePathwayToolCall, } from "./pathwayRuntime";
|
|
348
|
+
export { createVoicePathwaySlotCollector, DEFAULT_VOICE_PATHWAY_SLOT_PARSERS, } from "./pathwaySlotCollector";
|
|
349
|
+
export type { CreateVoicePathwaySlotCollectorOptions, VoicePathwaySlotCollector, VoicePathwaySlotCollectorAttempt, VoicePathwaySlotParser, VoicePathwaySlotParseResult, } from "./pathwaySlotCollector";
|
|
350
|
+
export { compileVoicePathwayToAssistant } from "./pathwayCompiler";
|
|
351
|
+
export type { CompileVoicePathwayOptions, VoicePathwayCompiledAssistant, VoicePathwayCompilerToolDefinition, } from "./pathwayCompiler";
|
|
352
|
+
export { renderVoicePathwayMermaid, renderVoicePathwayText, visualizeVoicePathway, } from "./pathwayVisualizer";
|
|
353
|
+
export type { VoicePathwayVisualization } from "./pathwayVisualizer";
|
|
344
354
|
export * from "./types";
|
package/dist/index.js
CHANGED
|
@@ -50589,6 +50589,777 @@ var detectVoiceQualityDrift = (input) => {
|
|
|
50589
50589
|
scope: { from: baselineFrom, to: cutoff }
|
|
50590
50590
|
};
|
|
50591
50591
|
};
|
|
50592
|
+
// src/pathway.ts
|
|
50593
|
+
var slotRefsInActions = (actions) => {
|
|
50594
|
+
if (!actions)
|
|
50595
|
+
return [];
|
|
50596
|
+
const refs = [];
|
|
50597
|
+
for (const action of actions) {
|
|
50598
|
+
if (action.kind === "collect-slot")
|
|
50599
|
+
refs.push(action.slotId);
|
|
50600
|
+
if (action.kind === "set-slot")
|
|
50601
|
+
refs.push(action.slotId);
|
|
50602
|
+
if (action.kind === "call-tool" && action.argsFromSlots) {
|
|
50603
|
+
refs.push(...action.argsFromSlots);
|
|
50604
|
+
}
|
|
50605
|
+
}
|
|
50606
|
+
return refs;
|
|
50607
|
+
};
|
|
50608
|
+
var slotRefsInCondition = (condition) => {
|
|
50609
|
+
if (condition.kind === "always" || condition.kind === "fallback")
|
|
50610
|
+
return [];
|
|
50611
|
+
return [condition.slotId];
|
|
50612
|
+
};
|
|
50613
|
+
var validateVoicePathway = (pathway) => {
|
|
50614
|
+
const issues = [];
|
|
50615
|
+
const stateIds = new Set;
|
|
50616
|
+
for (const state of pathway.states) {
|
|
50617
|
+
if (stateIds.has(state.id)) {
|
|
50618
|
+
issues.push({
|
|
50619
|
+
code: "duplicate-state",
|
|
50620
|
+
message: `Duplicate state id: ${state.id}`,
|
|
50621
|
+
severity: "error",
|
|
50622
|
+
stateId: state.id
|
|
50623
|
+
});
|
|
50624
|
+
}
|
|
50625
|
+
stateIds.add(state.id);
|
|
50626
|
+
}
|
|
50627
|
+
const slotIds = new Set;
|
|
50628
|
+
for (const slot of pathway.slots) {
|
|
50629
|
+
if (slotIds.has(slot.id)) {
|
|
50630
|
+
issues.push({
|
|
50631
|
+
code: "duplicate-slot",
|
|
50632
|
+
message: `Duplicate slot id: ${slot.id}`,
|
|
50633
|
+
severity: "error",
|
|
50634
|
+
slotId: slot.id
|
|
50635
|
+
});
|
|
50636
|
+
}
|
|
50637
|
+
slotIds.add(slot.id);
|
|
50638
|
+
}
|
|
50639
|
+
if (!stateIds.has(pathway.entryStateId)) {
|
|
50640
|
+
issues.push({
|
|
50641
|
+
code: "unknown-entry",
|
|
50642
|
+
message: `Entry state ${pathway.entryStateId} is not defined`,
|
|
50643
|
+
severity: "error"
|
|
50644
|
+
});
|
|
50645
|
+
}
|
|
50646
|
+
for (const state of pathway.states) {
|
|
50647
|
+
const seenTransitionKeys = new Set;
|
|
50648
|
+
state.transitions.forEach((transition, index) => {
|
|
50649
|
+
if (!stateIds.has(transition.to)) {
|
|
50650
|
+
issues.push({
|
|
50651
|
+
code: "missing-transition-target",
|
|
50652
|
+
message: `State ${state.id} transitions to unknown state ${transition.to}`,
|
|
50653
|
+
severity: "error",
|
|
50654
|
+
stateId: state.id
|
|
50655
|
+
});
|
|
50656
|
+
}
|
|
50657
|
+
const key = `${transition.to}::${transition.condition.kind}::${"slotId" in transition.condition ? transition.condition.slotId : ""}`;
|
|
50658
|
+
if (seenTransitionKeys.has(key)) {
|
|
50659
|
+
issues.push({
|
|
50660
|
+
code: "duplicate-transition",
|
|
50661
|
+
message: `State ${state.id} has duplicate transition to ${transition.to}`,
|
|
50662
|
+
severity: "warning",
|
|
50663
|
+
stateId: state.id
|
|
50664
|
+
});
|
|
50665
|
+
}
|
|
50666
|
+
seenTransitionKeys.add(key);
|
|
50667
|
+
if (transition.condition.kind === "fallback" && index !== state.transitions.length - 1) {
|
|
50668
|
+
issues.push({
|
|
50669
|
+
code: "fallback-not-last",
|
|
50670
|
+
message: `Fallback transition in ${state.id} must be the last transition`,
|
|
50671
|
+
severity: "error",
|
|
50672
|
+
stateId: state.id
|
|
50673
|
+
});
|
|
50674
|
+
}
|
|
50675
|
+
for (const ref of slotRefsInCondition(transition.condition)) {
|
|
50676
|
+
if (!slotIds.has(ref)) {
|
|
50677
|
+
issues.push({
|
|
50678
|
+
code: "missing-slot-ref",
|
|
50679
|
+
message: `Transition condition references unknown slot ${ref}`,
|
|
50680
|
+
severity: "error",
|
|
50681
|
+
slotId: ref,
|
|
50682
|
+
stateId: state.id
|
|
50683
|
+
});
|
|
50684
|
+
}
|
|
50685
|
+
}
|
|
50686
|
+
});
|
|
50687
|
+
for (const ref of slotRefsInActions(state.actions)) {
|
|
50688
|
+
if (!slotIds.has(ref)) {
|
|
50689
|
+
issues.push({
|
|
50690
|
+
code: "missing-slot-ref",
|
|
50691
|
+
message: `Action in ${state.id} references unknown slot ${ref}`,
|
|
50692
|
+
severity: "error",
|
|
50693
|
+
slotId: ref,
|
|
50694
|
+
stateId: state.id
|
|
50695
|
+
});
|
|
50696
|
+
}
|
|
50697
|
+
}
|
|
50698
|
+
}
|
|
50699
|
+
const reachable = new Set;
|
|
50700
|
+
const queue = stateIds.has(pathway.entryStateId) ? [pathway.entryStateId] : [];
|
|
50701
|
+
const stateById = new Map(pathway.states.map((s) => [s.id, s]));
|
|
50702
|
+
while (queue.length > 0) {
|
|
50703
|
+
const id = queue.shift();
|
|
50704
|
+
if (reachable.has(id))
|
|
50705
|
+
continue;
|
|
50706
|
+
reachable.add(id);
|
|
50707
|
+
const state = stateById.get(id);
|
|
50708
|
+
if (!state)
|
|
50709
|
+
continue;
|
|
50710
|
+
for (const transition of state.transitions) {
|
|
50711
|
+
if (stateIds.has(transition.to) && !reachable.has(transition.to)) {
|
|
50712
|
+
queue.push(transition.to);
|
|
50713
|
+
}
|
|
50714
|
+
}
|
|
50715
|
+
}
|
|
50716
|
+
for (const state of pathway.states) {
|
|
50717
|
+
if (!reachable.has(state.id)) {
|
|
50718
|
+
issues.push({
|
|
50719
|
+
code: "unreachable-state",
|
|
50720
|
+
message: `State ${state.id} is not reachable from entry ${pathway.entryStateId}`,
|
|
50721
|
+
severity: "warning",
|
|
50722
|
+
stateId: state.id
|
|
50723
|
+
});
|
|
50724
|
+
}
|
|
50725
|
+
}
|
|
50726
|
+
const hasReachableTerminal = pathway.states.some((state) => reachable.has(state.id) && (state.kind === "terminal" || state.transitions.length === 0));
|
|
50727
|
+
if (!hasReachableTerminal) {
|
|
50728
|
+
issues.push({
|
|
50729
|
+
code: "no-terminal-reachable",
|
|
50730
|
+
message: "No terminal state is reachable; pathway has no exit condition",
|
|
50731
|
+
severity: "error"
|
|
50732
|
+
});
|
|
50733
|
+
}
|
|
50734
|
+
const fatal = issues.some((i) => i.severity === "error");
|
|
50735
|
+
return {
|
|
50736
|
+
issues,
|
|
50737
|
+
reachableStates: Array.from(reachable),
|
|
50738
|
+
valid: !fatal
|
|
50739
|
+
};
|
|
50740
|
+
};
|
|
50741
|
+
var findVoicePathwayState = (pathway, id) => pathway.states.find((s) => s.id === id) ?? null;
|
|
50742
|
+
var findVoicePathwaySlot = (pathway, id) => pathway.slots.find((s) => s.id === id) ?? null;
|
|
50743
|
+
// src/pathwayRuntime.ts
|
|
50744
|
+
var evaluateCondition = (condition, slots) => {
|
|
50745
|
+
switch (condition.kind) {
|
|
50746
|
+
case "always":
|
|
50747
|
+
return true;
|
|
50748
|
+
case "fallback":
|
|
50749
|
+
return true;
|
|
50750
|
+
case "slot-filled":
|
|
50751
|
+
return slots[condition.slotId] !== undefined && slots[condition.slotId] !== null && slots[condition.slotId] !== "";
|
|
50752
|
+
case "slot-equals":
|
|
50753
|
+
return slots[condition.slotId] === condition.value;
|
|
50754
|
+
case "slot-matches": {
|
|
50755
|
+
const raw = slots[condition.slotId];
|
|
50756
|
+
if (typeof raw !== "string")
|
|
50757
|
+
return false;
|
|
50758
|
+
try {
|
|
50759
|
+
return new RegExp(condition.pattern, "u").test(raw);
|
|
50760
|
+
} catch {
|
|
50761
|
+
return false;
|
|
50762
|
+
}
|
|
50763
|
+
}
|
|
50764
|
+
}
|
|
50765
|
+
};
|
|
50766
|
+
var pickTransition = (state, slots) => {
|
|
50767
|
+
for (const transition of state.transitions) {
|
|
50768
|
+
if (transition.condition.kind === "fallback")
|
|
50769
|
+
continue;
|
|
50770
|
+
if (evaluateCondition(transition.condition, slots))
|
|
50771
|
+
return transition;
|
|
50772
|
+
}
|
|
50773
|
+
const fallback = state.transitions.find((t) => t.condition.kind === "fallback");
|
|
50774
|
+
return fallback ?? null;
|
|
50775
|
+
};
|
|
50776
|
+
var buildPendingActions = (state, slots, pathway, emit2) => {
|
|
50777
|
+
const pending = [];
|
|
50778
|
+
let awaitingSlotId = null;
|
|
50779
|
+
for (const action of state.actions ?? []) {
|
|
50780
|
+
if (action.kind === "say") {
|
|
50781
|
+
emit2({ text: action.text, type: "say" });
|
|
50782
|
+
continue;
|
|
50783
|
+
}
|
|
50784
|
+
if (action.kind === "collect-slot") {
|
|
50785
|
+
if (slots[action.slotId] === undefined || slots[action.slotId] === null) {
|
|
50786
|
+
const slot = findVoicePathwaySlot(pathway, action.slotId);
|
|
50787
|
+
emit2({
|
|
50788
|
+
prompt: slot?.prompt ?? `Please provide ${action.slotId}.`,
|
|
50789
|
+
slotId: action.slotId,
|
|
50790
|
+
type: "ask-slot"
|
|
50791
|
+
});
|
|
50792
|
+
awaitingSlotId = action.slotId;
|
|
50793
|
+
break;
|
|
50794
|
+
}
|
|
50795
|
+
continue;
|
|
50796
|
+
}
|
|
50797
|
+
if (action.kind === "call-tool") {
|
|
50798
|
+
const args = {};
|
|
50799
|
+
for (const id of action.argsFromSlots ?? []) {
|
|
50800
|
+
args[id] = slots[id] ?? null;
|
|
50801
|
+
}
|
|
50802
|
+
emit2({ call: { args, toolId: action.toolId }, type: "tool-call" });
|
|
50803
|
+
continue;
|
|
50804
|
+
}
|
|
50805
|
+
if (action.kind === "set-slot") {
|
|
50806
|
+
pending.push(action);
|
|
50807
|
+
continue;
|
|
50808
|
+
}
|
|
50809
|
+
if (action.kind === "transfer") {
|
|
50810
|
+
emit2({ destination: action.destination, type: "transfer" });
|
|
50811
|
+
return { actions: pending, awaitingSlotId: null, ended: true, reason: `transfer:${action.destination}` };
|
|
50812
|
+
}
|
|
50813
|
+
if (action.kind === "end-call") {
|
|
50814
|
+
emit2({ ...action.reason !== undefined ? { reason: action.reason } : {}, type: "end-call" });
|
|
50815
|
+
return { actions: pending, awaitingSlotId: null, ended: true, ...action.reason !== undefined ? { reason: action.reason } : {} };
|
|
50816
|
+
}
|
|
50817
|
+
}
|
|
50818
|
+
return { actions: pending, awaitingSlotId, ended: false };
|
|
50819
|
+
};
|
|
50820
|
+
var createVoicePathwayRuntime = (options) => {
|
|
50821
|
+
if (!options.skipValidation) {
|
|
50822
|
+
const report = validateVoicePathway(options.pathway);
|
|
50823
|
+
if (!report.valid) {
|
|
50824
|
+
throw new Error(`Invalid pathway: ${report.issues.filter((i) => i.severity === "error").map((i) => i.message).join("; ")}`);
|
|
50825
|
+
}
|
|
50826
|
+
}
|
|
50827
|
+
const now = options.now ?? (() => Date.now());
|
|
50828
|
+
const listeners = new Set;
|
|
50829
|
+
const emit2 = (event) => {
|
|
50830
|
+
for (const l of listeners)
|
|
50831
|
+
l(event);
|
|
50832
|
+
};
|
|
50833
|
+
let state = {
|
|
50834
|
+
awaitingSlotId: null,
|
|
50835
|
+
currentStateId: options.pathway.entryStateId,
|
|
50836
|
+
history: [],
|
|
50837
|
+
pendingActions: [],
|
|
50838
|
+
slots: { ...options.initialSlots ?? {} },
|
|
50839
|
+
status: "ready"
|
|
50840
|
+
};
|
|
50841
|
+
const enter = (stateId) => {
|
|
50842
|
+
const target = findVoicePathwayState(options.pathway, stateId);
|
|
50843
|
+
if (!target) {
|
|
50844
|
+
state = { ...state, lastError: `Unknown state ${stateId}`, status: "errored" };
|
|
50845
|
+
emit2({ message: state.lastError, type: "errored" });
|
|
50846
|
+
return;
|
|
50847
|
+
}
|
|
50848
|
+
state = {
|
|
50849
|
+
...state,
|
|
50850
|
+
currentStateId: stateId,
|
|
50851
|
+
history: [...state.history, { at: now(), stateId }],
|
|
50852
|
+
pendingActions: []
|
|
50853
|
+
};
|
|
50854
|
+
emit2({ stateId, type: "state-entered" });
|
|
50855
|
+
const result = buildPendingActions(target, state.slots, options.pathway, emit2);
|
|
50856
|
+
state = {
|
|
50857
|
+
...state,
|
|
50858
|
+
awaitingSlotId: result.awaitingSlotId,
|
|
50859
|
+
pendingActions: result.actions,
|
|
50860
|
+
status: result.ended ? "ended" : result.awaitingSlotId ? "awaiting-slot" : "branching",
|
|
50861
|
+
...result.reason !== undefined ? { endedReason: result.reason } : {}
|
|
50862
|
+
};
|
|
50863
|
+
if (state.status === "branching")
|
|
50864
|
+
tryTransition();
|
|
50865
|
+
};
|
|
50866
|
+
const tryTransition = () => {
|
|
50867
|
+
const current = findVoicePathwayState(options.pathway, state.currentStateId);
|
|
50868
|
+
if (!current)
|
|
50869
|
+
return;
|
|
50870
|
+
if (current.transitions.length === 0) {
|
|
50871
|
+
state = { ...state, status: "ended" };
|
|
50872
|
+
return;
|
|
50873
|
+
}
|
|
50874
|
+
const transition = pickTransition(current, state.slots);
|
|
50875
|
+
if (!transition) {
|
|
50876
|
+
state = { ...state, status: "awaiting-slot" };
|
|
50877
|
+
return;
|
|
50878
|
+
}
|
|
50879
|
+
enter(transition.to);
|
|
50880
|
+
};
|
|
50881
|
+
const start = () => {
|
|
50882
|
+
state.history = [];
|
|
50883
|
+
enter(options.pathway.entryStateId);
|
|
50884
|
+
};
|
|
50885
|
+
const fillSlot = (slotId, value) => {
|
|
50886
|
+
state = { ...state, slots: { ...state.slots, [slotId]: value } };
|
|
50887
|
+
if (state.awaitingSlotId === slotId) {
|
|
50888
|
+
state = { ...state, awaitingSlotId: null, status: "branching" };
|
|
50889
|
+
const current = findVoicePathwayState(options.pathway, state.currentStateId);
|
|
50890
|
+
if (current) {
|
|
50891
|
+
const result = buildPendingActions(current, state.slots, options.pathway, emit2);
|
|
50892
|
+
state = {
|
|
50893
|
+
...state,
|
|
50894
|
+
awaitingSlotId: result.awaitingSlotId,
|
|
50895
|
+
pendingActions: result.actions,
|
|
50896
|
+
status: result.ended ? "ended" : result.awaitingSlotId ? "awaiting-slot" : "branching",
|
|
50897
|
+
...result.reason !== undefined ? { endedReason: result.reason } : {}
|
|
50898
|
+
};
|
|
50899
|
+
if (state.status === "branching")
|
|
50900
|
+
tryTransition();
|
|
50901
|
+
}
|
|
50902
|
+
}
|
|
50903
|
+
};
|
|
50904
|
+
return {
|
|
50905
|
+
fillSlot,
|
|
50906
|
+
getState: () => state,
|
|
50907
|
+
start,
|
|
50908
|
+
subscribe(listener) {
|
|
50909
|
+
listeners.add(listener);
|
|
50910
|
+
return () => {
|
|
50911
|
+
listeners.delete(listener);
|
|
50912
|
+
};
|
|
50913
|
+
},
|
|
50914
|
+
tryTransition
|
|
50915
|
+
};
|
|
50916
|
+
};
|
|
50917
|
+
// src/pathwaySlotCollector.ts
|
|
50918
|
+
var numberWords = {
|
|
50919
|
+
eight: 8,
|
|
50920
|
+
five: 5,
|
|
50921
|
+
four: 4,
|
|
50922
|
+
nine: 9,
|
|
50923
|
+
one: 1,
|
|
50924
|
+
seven: 7,
|
|
50925
|
+
six: 6,
|
|
50926
|
+
ten: 10,
|
|
50927
|
+
three: 3,
|
|
50928
|
+
two: 2,
|
|
50929
|
+
zero: 0
|
|
50930
|
+
};
|
|
50931
|
+
var parseString = (raw, slot) => {
|
|
50932
|
+
const trimmed = raw.trim();
|
|
50933
|
+
if (!trimmed)
|
|
50934
|
+
return { ok: false, reason: "empty" };
|
|
50935
|
+
const min = slot.validation?.minLength ?? 1;
|
|
50936
|
+
const max = slot.validation?.maxLength ?? Number.MAX_SAFE_INTEGER;
|
|
50937
|
+
if (trimmed.length < min || trimmed.length > max) {
|
|
50938
|
+
return {
|
|
50939
|
+
hint: `Expected ${min}\u2013${max} characters`,
|
|
50940
|
+
ok: false,
|
|
50941
|
+
reason: "out-of-range"
|
|
50942
|
+
};
|
|
50943
|
+
}
|
|
50944
|
+
if (slot.validation?.pattern) {
|
|
50945
|
+
try {
|
|
50946
|
+
if (!new RegExp(slot.validation.pattern, "u").test(trimmed)) {
|
|
50947
|
+
return { ok: false, reason: "no-match" };
|
|
50948
|
+
}
|
|
50949
|
+
} catch {
|
|
50950
|
+
return { ok: false, reason: "no-match" };
|
|
50951
|
+
}
|
|
50952
|
+
}
|
|
50953
|
+
return { normalized: trimmed, ok: true, value: trimmed };
|
|
50954
|
+
};
|
|
50955
|
+
var parseNumber = (raw, slot) => {
|
|
50956
|
+
const trimmed = raw.trim().toLowerCase();
|
|
50957
|
+
if (!trimmed)
|
|
50958
|
+
return { ok: false, reason: "empty" };
|
|
50959
|
+
let value;
|
|
50960
|
+
if (numberWords[trimmed] !== undefined) {
|
|
50961
|
+
value = numberWords[trimmed];
|
|
50962
|
+
} else {
|
|
50963
|
+
const cleaned = trimmed.replace(/[$,]/gu, "");
|
|
50964
|
+
value = Number(cleaned);
|
|
50965
|
+
if (Number.isNaN(value))
|
|
50966
|
+
return { ok: false, reason: "type-mismatch" };
|
|
50967
|
+
}
|
|
50968
|
+
const min = slot.validation?.min ?? Number.NEGATIVE_INFINITY;
|
|
50969
|
+
const max = slot.validation?.max ?? Number.POSITIVE_INFINITY;
|
|
50970
|
+
if (value < min || value > max) {
|
|
50971
|
+
return { hint: `Expected ${min}\u2013${max}`, ok: false, reason: "out-of-range" };
|
|
50972
|
+
}
|
|
50973
|
+
return { normalized: String(value), ok: true, value };
|
|
50974
|
+
};
|
|
50975
|
+
var parseBoolean2 = (raw) => {
|
|
50976
|
+
const trimmed = raw.trim().toLowerCase();
|
|
50977
|
+
if (!trimmed)
|
|
50978
|
+
return { ok: false, reason: "empty" };
|
|
50979
|
+
if (["yes", "yeah", "yep", "correct", "true", "sure", "ok"].includes(trimmed)) {
|
|
50980
|
+
return { normalized: "true", ok: true, value: true };
|
|
50981
|
+
}
|
|
50982
|
+
if (["no", "nope", "nah", "false", "incorrect"].includes(trimmed)) {
|
|
50983
|
+
return { normalized: "false", ok: true, value: false };
|
|
50984
|
+
}
|
|
50985
|
+
return { ok: false, reason: "type-mismatch" };
|
|
50986
|
+
};
|
|
50987
|
+
var parseDate = (raw) => {
|
|
50988
|
+
const trimmed = raw.trim();
|
|
50989
|
+
if (!trimmed)
|
|
50990
|
+
return { ok: false, reason: "empty" };
|
|
50991
|
+
const date = new Date(trimmed);
|
|
50992
|
+
if (Number.isNaN(date.getTime()))
|
|
50993
|
+
return { ok: false, reason: "type-mismatch" };
|
|
50994
|
+
const iso = date.toISOString().slice(0, 10);
|
|
50995
|
+
return { normalized: iso, ok: true, value: iso };
|
|
50996
|
+
};
|
|
50997
|
+
var parseTime2 = (raw) => {
|
|
50998
|
+
const trimmed = raw.trim();
|
|
50999
|
+
const match = /^([0-9]{1,2}):?([0-9]{2})?\s*(am|pm)?$/iu.exec(trimmed);
|
|
51000
|
+
if (!match)
|
|
51001
|
+
return { ok: false, reason: "type-mismatch" };
|
|
51002
|
+
let hour = Number(match[1]);
|
|
51003
|
+
const minute = Number(match[2] ?? "0");
|
|
51004
|
+
const meridiem = match[3]?.toLowerCase();
|
|
51005
|
+
if (meridiem === "pm" && hour < 12)
|
|
51006
|
+
hour += 12;
|
|
51007
|
+
if (meridiem === "am" && hour === 12)
|
|
51008
|
+
hour = 0;
|
|
51009
|
+
if (hour > 23 || minute > 59)
|
|
51010
|
+
return { ok: false, reason: "out-of-range" };
|
|
51011
|
+
const formatted = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}`;
|
|
51012
|
+
return { normalized: formatted, ok: true, value: formatted };
|
|
51013
|
+
};
|
|
51014
|
+
var parsePhone = (raw) => {
|
|
51015
|
+
const trimmed = raw.trim();
|
|
51016
|
+
if (!trimmed)
|
|
51017
|
+
return { ok: false, reason: "empty" };
|
|
51018
|
+
const digits = trimmed.replace(/\D/gu, "");
|
|
51019
|
+
if (digits.length < 10 || digits.length > 15) {
|
|
51020
|
+
return { ok: false, reason: "type-mismatch" };
|
|
51021
|
+
}
|
|
51022
|
+
const normalized = digits.length === 10 ? `+1${digits}` : `+${digits}`;
|
|
51023
|
+
return { normalized, ok: true, value: normalized };
|
|
51024
|
+
};
|
|
51025
|
+
var parseEmail = (raw) => {
|
|
51026
|
+
const trimmed = raw.trim();
|
|
51027
|
+
if (!trimmed)
|
|
51028
|
+
return { ok: false, reason: "empty" };
|
|
51029
|
+
const collapsed = trimmed.replace(/\s+at\s+/gu, "@").replace(/\s+dot\s+/gu, ".");
|
|
51030
|
+
const ok = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/iu.test(collapsed);
|
|
51031
|
+
if (!ok)
|
|
51032
|
+
return { ok: false, reason: "type-mismatch" };
|
|
51033
|
+
return { normalized: collapsed.toLowerCase(), ok: true, value: collapsed.toLowerCase() };
|
|
51034
|
+
};
|
|
51035
|
+
var parseCurrency = (raw, slot) => {
|
|
51036
|
+
const trimmed = raw.trim().toLowerCase();
|
|
51037
|
+
if (!trimmed)
|
|
51038
|
+
return { ok: false, reason: "empty" };
|
|
51039
|
+
const cleaned = trimmed.replace(/[$,]/gu, "").replace(/\s*(dollars?|usd)$/u, "");
|
|
51040
|
+
const value = Number(cleaned);
|
|
51041
|
+
if (Number.isNaN(value))
|
|
51042
|
+
return { ok: false, reason: "type-mismatch" };
|
|
51043
|
+
const min = slot.validation?.min ?? 0;
|
|
51044
|
+
const max = slot.validation?.max ?? Number.POSITIVE_INFINITY;
|
|
51045
|
+
if (value < min || value > max) {
|
|
51046
|
+
return { hint: `Expected ${min}\u2013${max}`, ok: false, reason: "out-of-range" };
|
|
51047
|
+
}
|
|
51048
|
+
return { normalized: value.toFixed(2), ok: true, value };
|
|
51049
|
+
};
|
|
51050
|
+
var parseChoice = (raw, slot) => {
|
|
51051
|
+
const trimmed = raw.trim().toLowerCase();
|
|
51052
|
+
if (!trimmed)
|
|
51053
|
+
return { ok: false, reason: "empty" };
|
|
51054
|
+
const match = (slot.choices ?? []).find((choice) => choice.toLowerCase() === trimmed);
|
|
51055
|
+
if (!match)
|
|
51056
|
+
return { ok: false, reason: "no-match" };
|
|
51057
|
+
return { normalized: match, ok: true, value: match };
|
|
51058
|
+
};
|
|
51059
|
+
var DEFAULT_VOICE_PATHWAY_SLOT_PARSERS = {
|
|
51060
|
+
boolean: parseBoolean2,
|
|
51061
|
+
choice: parseChoice,
|
|
51062
|
+
currency: parseCurrency,
|
|
51063
|
+
date: parseDate,
|
|
51064
|
+
email: parseEmail,
|
|
51065
|
+
number: parseNumber,
|
|
51066
|
+
phone: parsePhone,
|
|
51067
|
+
string: parseString,
|
|
51068
|
+
time: parseTime2
|
|
51069
|
+
};
|
|
51070
|
+
var createVoicePathwaySlotCollector = (options = {}) => {
|
|
51071
|
+
const parsers = {
|
|
51072
|
+
...DEFAULT_VOICE_PATHWAY_SLOT_PARSERS,
|
|
51073
|
+
...options.parsers ?? {}
|
|
51074
|
+
};
|
|
51075
|
+
const maxAttempts = options.maxAttemptsPerSlot ?? 3;
|
|
51076
|
+
const attemptCounts = new Map;
|
|
51077
|
+
const interpret = (slot, raw) => {
|
|
51078
|
+
const parser = parsers[slot.type];
|
|
51079
|
+
if (!parser) {
|
|
51080
|
+
return {
|
|
51081
|
+
attempt: (attemptCounts.get(slot.id) ?? 0) + 1,
|
|
51082
|
+
raw,
|
|
51083
|
+
result: {
|
|
51084
|
+
hint: `No parser for ${slot.type}`,
|
|
51085
|
+
ok: false,
|
|
51086
|
+
reason: "no-match"
|
|
51087
|
+
},
|
|
51088
|
+
slotId: slot.id
|
|
51089
|
+
};
|
|
51090
|
+
}
|
|
51091
|
+
const next = (attemptCounts.get(slot.id) ?? 0) + 1;
|
|
51092
|
+
attemptCounts.set(slot.id, next);
|
|
51093
|
+
return {
|
|
51094
|
+
attempt: next,
|
|
51095
|
+
raw,
|
|
51096
|
+
result: parser(raw, slot),
|
|
51097
|
+
slotId: slot.id
|
|
51098
|
+
};
|
|
51099
|
+
};
|
|
51100
|
+
const attemptsExceeded = (slotId) => (attemptCounts.get(slotId) ?? 0) >= maxAttempts;
|
|
51101
|
+
return {
|
|
51102
|
+
attemptsExceeded,
|
|
51103
|
+
interpret,
|
|
51104
|
+
reset: (slotId) => {
|
|
51105
|
+
if (slotId)
|
|
51106
|
+
attemptCounts.delete(slotId);
|
|
51107
|
+
else
|
|
51108
|
+
attemptCounts.clear();
|
|
51109
|
+
}
|
|
51110
|
+
};
|
|
51111
|
+
};
|
|
51112
|
+
// src/pathwayCompiler.ts
|
|
51113
|
+
var describeAction = (action) => {
|
|
51114
|
+
switch (action.kind) {
|
|
51115
|
+
case "say":
|
|
51116
|
+
return `Say to the caller: "${action.text}"`;
|
|
51117
|
+
case "collect-slot":
|
|
51118
|
+
return `Collect slot \`${action.slotId}\` from the caller.`;
|
|
51119
|
+
case "call-tool":
|
|
51120
|
+
return `Call tool \`${action.toolId}\`${action.argsFromSlots ? ` with slots: ${action.argsFromSlots.join(", ")}` : ""}.`;
|
|
51121
|
+
case "set-slot":
|
|
51122
|
+
return `Set slot \`${action.slotId}\` to expression \`${action.valueExpression}\`.`;
|
|
51123
|
+
case "transfer":
|
|
51124
|
+
return `Transfer the call to \`${action.destination}\`.`;
|
|
51125
|
+
case "end-call":
|
|
51126
|
+
return `End the call${action.reason ? ` (reason: ${action.reason})` : ""}.`;
|
|
51127
|
+
}
|
|
51128
|
+
};
|
|
51129
|
+
var describeCondition = (condition) => {
|
|
51130
|
+
switch (condition.kind) {
|
|
51131
|
+
case "always":
|
|
51132
|
+
return "always";
|
|
51133
|
+
case "fallback":
|
|
51134
|
+
return "if no other condition matches";
|
|
51135
|
+
case "slot-filled":
|
|
51136
|
+
return `when slot \`${condition.slotId}\` is filled`;
|
|
51137
|
+
case "slot-equals":
|
|
51138
|
+
return `when slot \`${condition.slotId}\` equals ${JSON.stringify(condition.value)}`;
|
|
51139
|
+
case "slot-matches":
|
|
51140
|
+
return `when slot \`${condition.slotId}\` matches /${condition.pattern}/`;
|
|
51141
|
+
}
|
|
51142
|
+
};
|
|
51143
|
+
var describeTransition = (transition) => `\u2192 ${transition.to} (${describeCondition(transition.condition)})`;
|
|
51144
|
+
var describeState = (state) => {
|
|
51145
|
+
const header = `## State: ${state.id} \u2014 ${state.label}${state.kind ? ` (${state.kind})` : ""}`;
|
|
51146
|
+
const description = state.description ? `
|
|
51147
|
+
${state.description}` : "";
|
|
51148
|
+
const note = state.systemNote ? `
|
|
51149
|
+
Note: ${state.systemNote}` : "";
|
|
51150
|
+
const actions = (state.actions ?? []).map((a) => `- ${describeAction(a)}`).join(`
|
|
51151
|
+
`);
|
|
51152
|
+
const transitions = state.transitions.map((t) => `- ${describeTransition(t)}`).join(`
|
|
51153
|
+
`);
|
|
51154
|
+
return [
|
|
51155
|
+
header,
|
|
51156
|
+
description,
|
|
51157
|
+
note,
|
|
51158
|
+
actions ? `
|
|
51159
|
+
Actions:
|
|
51160
|
+
${actions}` : "",
|
|
51161
|
+
transitions ? `
|
|
51162
|
+
Transitions:
|
|
51163
|
+
${transitions}` : ""
|
|
51164
|
+
].filter((s) => s.length > 0).join(`
|
|
51165
|
+
`);
|
|
51166
|
+
};
|
|
51167
|
+
var buildTools = (pathway, prefix) => {
|
|
51168
|
+
const advanceTool = {
|
|
51169
|
+
description: `Advance the ${pathway.label} pathway by transitioning to the next state. Always call when a state's conditions are met.`,
|
|
51170
|
+
name: `${prefix}_advance`,
|
|
51171
|
+
parameters: {
|
|
51172
|
+
properties: {
|
|
51173
|
+
rationale: {
|
|
51174
|
+
description: "Why this transition is being taken now.",
|
|
51175
|
+
type: "string"
|
|
51176
|
+
},
|
|
51177
|
+
toStateId: {
|
|
51178
|
+
description: "ID of the target state.",
|
|
51179
|
+
enum: pathway.states.map((s) => s.id),
|
|
51180
|
+
type: "string"
|
|
51181
|
+
}
|
|
51182
|
+
},
|
|
51183
|
+
required: ["toStateId"],
|
|
51184
|
+
type: "object"
|
|
51185
|
+
}
|
|
51186
|
+
};
|
|
51187
|
+
const fillSlotTool = {
|
|
51188
|
+
description: `Record a slot value collected from the caller within the ${pathway.label} pathway.`,
|
|
51189
|
+
name: `${prefix}_fill_slot`,
|
|
51190
|
+
parameters: {
|
|
51191
|
+
properties: {
|
|
51192
|
+
slotId: {
|
|
51193
|
+
description: "ID of the slot being filled.",
|
|
51194
|
+
enum: pathway.slots.map((s) => s.id),
|
|
51195
|
+
type: "string"
|
|
51196
|
+
},
|
|
51197
|
+
value: {
|
|
51198
|
+
description: "The value the caller provided.",
|
|
51199
|
+
type: "string"
|
|
51200
|
+
}
|
|
51201
|
+
},
|
|
51202
|
+
required: ["slotId", "value"],
|
|
51203
|
+
type: "object"
|
|
51204
|
+
}
|
|
51205
|
+
};
|
|
51206
|
+
const endCallTool = {
|
|
51207
|
+
description: `End the ${pathway.label} call.`,
|
|
51208
|
+
name: `${prefix}_end_call`,
|
|
51209
|
+
parameters: {
|
|
51210
|
+
properties: {
|
|
51211
|
+
reason: { description: "Reason for ending.", type: "string" }
|
|
51212
|
+
},
|
|
51213
|
+
required: [],
|
|
51214
|
+
type: "object"
|
|
51215
|
+
}
|
|
51216
|
+
};
|
|
51217
|
+
const userTools = (pathway.tools ?? []).map((tool) => ({
|
|
51218
|
+
description: tool.description,
|
|
51219
|
+
name: `${prefix}_tool_${tool.id}`,
|
|
51220
|
+
parameters: {
|
|
51221
|
+
properties: {
|
|
51222
|
+
arguments: { description: "JSON-encoded arguments.", type: "string" }
|
|
51223
|
+
},
|
|
51224
|
+
required: [],
|
|
51225
|
+
type: "object"
|
|
51226
|
+
}
|
|
51227
|
+
}));
|
|
51228
|
+
return [advanceTool, fillSlotTool, endCallTool, ...userTools];
|
|
51229
|
+
};
|
|
51230
|
+
var compileVoicePathwayToAssistant = (options) => {
|
|
51231
|
+
const report = validateVoicePathway(options.pathway);
|
|
51232
|
+
if (!report.valid) {
|
|
51233
|
+
throw new Error(`Cannot compile invalid pathway: ${report.issues.filter((i) => i.severity === "error").map((i) => i.message).join("; ")}`);
|
|
51234
|
+
}
|
|
51235
|
+
const prefix = options.toolNamePrefix ?? `pathway_${options.pathway.id}`;
|
|
51236
|
+
const tools = buildTools(options.pathway, prefix);
|
|
51237
|
+
const fallback = options.fallbackBehavior === "end-call" ? "If no transition condition is met, end the call politely." : options.fallbackBehavior === "transfer" ? "If no transition condition is met, transfer to a human agent." : "If no transition condition is met, ask a clarifying question and stay in the current state.";
|
|
51238
|
+
const slotBlock = options.pathway.slots.map((slot) => `- \`${slot.id}\` (${slot.type}${slot.required ? ", required" : ""}): ${slot.description ?? slot.prompt}`).join(`
|
|
51239
|
+
`);
|
|
51240
|
+
const stateBlock = options.pathway.states.map(describeState).join(`
|
|
51241
|
+
|
|
51242
|
+
`);
|
|
51243
|
+
const systemPrompt = [
|
|
51244
|
+
`You are operating the "${options.pathway.label}" pathway as a voice agent.`,
|
|
51245
|
+
`Follow the state machine exactly. Use the provided tools to advance states, fill slots, and end the call.`,
|
|
51246
|
+
`Start in state \`${options.pathway.entryStateId}\`. Track which state you are in. Do not invent states or transitions.`,
|
|
51247
|
+
fallback,
|
|
51248
|
+
"",
|
|
51249
|
+
`Slots:
|
|
51250
|
+
${slotBlock || "(none)"}`,
|
|
51251
|
+
"",
|
|
51252
|
+
`States:
|
|
51253
|
+
${stateBlock}`
|
|
51254
|
+
].join(`
|
|
51255
|
+
`);
|
|
51256
|
+
return {
|
|
51257
|
+
metadata: {
|
|
51258
|
+
entryStateId: options.pathway.entryStateId,
|
|
51259
|
+
pathwayId: options.pathway.id,
|
|
51260
|
+
pathwayLabel: options.pathway.label
|
|
51261
|
+
},
|
|
51262
|
+
systemPrompt,
|
|
51263
|
+
tools,
|
|
51264
|
+
...options.introduction !== undefined ? { initialPrompt: options.introduction } : {}
|
|
51265
|
+
};
|
|
51266
|
+
};
|
|
51267
|
+
// src/pathwayVisualizer.ts
|
|
51268
|
+
var escapeMermaidLabel = (text) => text.replace(/[<>"]/gu, "").replace(/\|/gu, "/");
|
|
51269
|
+
var conditionLabel = (condition) => {
|
|
51270
|
+
switch (condition.kind) {
|
|
51271
|
+
case "always":
|
|
51272
|
+
return "always";
|
|
51273
|
+
case "fallback":
|
|
51274
|
+
return "else";
|
|
51275
|
+
case "slot-filled":
|
|
51276
|
+
return `${condition.slotId} \u2713`;
|
|
51277
|
+
case "slot-equals":
|
|
51278
|
+
return `${condition.slotId}=${condition.value}`;
|
|
51279
|
+
case "slot-matches":
|
|
51280
|
+
return `${condition.slotId}~/${condition.pattern}/`;
|
|
51281
|
+
}
|
|
51282
|
+
};
|
|
51283
|
+
var mermaidShape = (state) => {
|
|
51284
|
+
switch (state.kind) {
|
|
51285
|
+
case "entry":
|
|
51286
|
+
return { close: ")", open: "((" };
|
|
51287
|
+
case "terminal":
|
|
51288
|
+
return { close: "))", open: "((" };
|
|
51289
|
+
case "branch":
|
|
51290
|
+
return { close: "}", open: "{" };
|
|
51291
|
+
case "action":
|
|
51292
|
+
return { close: "]", open: "[" };
|
|
51293
|
+
case "collect":
|
|
51294
|
+
return { close: "]", open: "[" };
|
|
51295
|
+
default:
|
|
51296
|
+
return { close: "]", open: "[" };
|
|
51297
|
+
}
|
|
51298
|
+
};
|
|
51299
|
+
var renderVoicePathwayMermaid = (pathway) => {
|
|
51300
|
+
const lines = ["flowchart TD"];
|
|
51301
|
+
for (const state of pathway.states) {
|
|
51302
|
+
const shape = mermaidShape(state);
|
|
51303
|
+
const label = escapeMermaidLabel(`${state.id}: ${state.label}`);
|
|
51304
|
+
lines.push(` ${state.id}${shape.open}"${label}"${shape.close}`);
|
|
51305
|
+
}
|
|
51306
|
+
for (const state of pathway.states) {
|
|
51307
|
+
for (const transition of state.transitions) {
|
|
51308
|
+
const label = escapeMermaidLabel(conditionLabel(transition.condition));
|
|
51309
|
+
lines.push(` ${state.id} -- "${label}" --> ${transition.to}`);
|
|
51310
|
+
}
|
|
51311
|
+
}
|
|
51312
|
+
return lines.join(`
|
|
51313
|
+
`);
|
|
51314
|
+
};
|
|
51315
|
+
var renderVoicePathwayText = (pathway) => {
|
|
51316
|
+
const stateById = new Map(pathway.states.map((s) => [s.id, s]));
|
|
51317
|
+
const visited = new Set;
|
|
51318
|
+
const lines = [
|
|
51319
|
+
`Pathway: ${pathway.label} (${pathway.id})`,
|
|
51320
|
+
`Entry: ${pathway.entryStateId}`,
|
|
51321
|
+
""
|
|
51322
|
+
];
|
|
51323
|
+
if (pathway.slots.length > 0) {
|
|
51324
|
+
lines.push("Slots:");
|
|
51325
|
+
for (const slot of pathway.slots) {
|
|
51326
|
+
lines.push(` - ${slot.id} (${slot.type}${slot.required ? ", required" : ""}): ${slot.prompt}`);
|
|
51327
|
+
}
|
|
51328
|
+
lines.push("");
|
|
51329
|
+
}
|
|
51330
|
+
const walk = (stateId, depth) => {
|
|
51331
|
+
if (visited.has(stateId)) {
|
|
51332
|
+
lines.push(`${" ".repeat(depth)}\u2192 ${stateId} (already shown)`);
|
|
51333
|
+
return;
|
|
51334
|
+
}
|
|
51335
|
+
visited.add(stateId);
|
|
51336
|
+
const state = stateById.get(stateId);
|
|
51337
|
+
if (!state) {
|
|
51338
|
+
lines.push(`${" ".repeat(depth)}\u2192 ${stateId} (missing)`);
|
|
51339
|
+
return;
|
|
51340
|
+
}
|
|
51341
|
+
const indent = " ".repeat(depth);
|
|
51342
|
+
const kind = state.kind ? ` [${state.kind}]` : "";
|
|
51343
|
+
lines.push(`${indent}- ${state.id}: ${state.label}${kind}`);
|
|
51344
|
+
if (state.actions && state.actions.length > 0) {
|
|
51345
|
+
for (const action of state.actions) {
|
|
51346
|
+
lines.push(`${indent} \xB7 ${action.kind}`);
|
|
51347
|
+
}
|
|
51348
|
+
}
|
|
51349
|
+
for (const transition of state.transitions) {
|
|
51350
|
+
lines.push(`${indent} \u2192 ${transition.to} (${conditionLabel(transition.condition)})`);
|
|
51351
|
+
walk(transition.to, depth + 1);
|
|
51352
|
+
}
|
|
51353
|
+
};
|
|
51354
|
+
lines.push("States:");
|
|
51355
|
+
walk(pathway.entryStateId, 1);
|
|
51356
|
+
return lines.join(`
|
|
51357
|
+
`);
|
|
51358
|
+
};
|
|
51359
|
+
var visualizeVoicePathway = (pathway) => ({
|
|
51360
|
+
mermaid: renderVoicePathwayMermaid(pathway),
|
|
51361
|
+
text: renderVoicePathwayText(pathway)
|
|
51362
|
+
});
|
|
50592
51363
|
export {
|
|
50593
51364
|
writeVoiceProofPack,
|
|
50594
51365
|
writeVoiceMediaPipelineArtifacts,
|
|
@@ -50603,12 +51374,14 @@ export {
|
|
|
50603
51374
|
voiceComplianceRedactionDefaults,
|
|
50604
51375
|
voiceAgentUIStateOrder,
|
|
50605
51376
|
voice,
|
|
51377
|
+
visualizeVoicePathway,
|
|
50606
51378
|
verifyVoiceWebhookSignature,
|
|
50607
51379
|
verifyVoiceTwilioWebhookSignature,
|
|
50608
51380
|
verifyVoiceTelnyxWebhookSignature,
|
|
50609
51381
|
verifyVoicePlivoWebhookSignature,
|
|
50610
51382
|
verifyVoiceOpsWebhookSignature,
|
|
50611
51383
|
validateVoiceWorkflowRouteResult,
|
|
51384
|
+
validateVoicePathway,
|
|
50612
51385
|
validateVoiceObservabilityExportRecord,
|
|
50613
51386
|
validateVoiceDTMFLuhn,
|
|
50614
51387
|
ttsAdapterSessionCanCancel,
|
|
@@ -50759,6 +51532,8 @@ export {
|
|
|
50759
51532
|
renderVoiceProductionReadinessHTML,
|
|
50760
51533
|
renderVoicePostCallAnalysisMarkdown,
|
|
50761
51534
|
renderVoicePhoneAgentProductionSmokeHTML,
|
|
51535
|
+
renderVoicePathwayText,
|
|
51536
|
+
renderVoicePathwayMermaid,
|
|
50762
51537
|
renderVoiceOutcomeContractHTML,
|
|
50763
51538
|
renderVoiceOpsStatusHTML,
|
|
50764
51539
|
renderVoiceOpsRecoveryMarkdown,
|
|
@@ -50872,6 +51647,8 @@ export {
|
|
|
50872
51647
|
fromVapiAssistantConfig,
|
|
50873
51648
|
formatVoiceProofTrendAge,
|
|
50874
51649
|
formatVoiceCallPlayerTimestamp,
|
|
51650
|
+
findVoicePathwayState,
|
|
51651
|
+
findVoicePathwaySlot,
|
|
50875
51652
|
filterVoiceTraceEvents,
|
|
50876
51653
|
filterVoiceAuditEvents,
|
|
50877
51654
|
fetchVoiceProofTarget,
|
|
@@ -51127,6 +51904,8 @@ export {
|
|
|
51127
51904
|
createVoicePhoneAgentProductionSmokeJSONHandler,
|
|
51128
51905
|
createVoicePhoneAgentProductionSmokeHTMLHandler,
|
|
51129
51906
|
createVoicePhoneAgent,
|
|
51907
|
+
createVoicePathwaySlotCollector,
|
|
51908
|
+
createVoicePathwayRuntime,
|
|
51130
51909
|
createVoiceOutcomeContractRoutes,
|
|
51131
51910
|
createVoiceOutcomeContractJSONHandler,
|
|
51132
51911
|
createVoiceOutcomeContractHTMLHandler,
|
|
@@ -51332,6 +52111,7 @@ export {
|
|
|
51332
52111
|
computeVoiceScorecardCalibration,
|
|
51333
52112
|
computePcmDurationMs,
|
|
51334
52113
|
completeVoiceOpsTask,
|
|
52114
|
+
compileVoicePathwayToAssistant,
|
|
51335
52115
|
compareVoiceEvalBaseline,
|
|
51336
52116
|
compareVoiceCostScenarios,
|
|
51337
52117
|
collectVoiceDTMFInput,
|
|
@@ -51502,6 +52282,7 @@ export {
|
|
|
51502
52282
|
DEFAULT_VOICE_PROMPT_INJECTION_RULES,
|
|
51503
52283
|
DEFAULT_VOICE_PRICE_BOOK,
|
|
51504
52284
|
DEFAULT_VOICE_POST_CALL_SURVEY_QUESTIONS,
|
|
52285
|
+
DEFAULT_VOICE_PATHWAY_SLOT_PARSERS,
|
|
51505
52286
|
DEFAULT_VOICE_CAMPAIGN_TEMPLATE_FILTERS,
|
|
51506
52287
|
DEFAULT_VOICE_CALL_DISPOSITIONS,
|
|
51507
52288
|
DEFAULT_VOICE_ANNOTATION_KIND_SEVERITY,
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export type VoicePathwaySlotType = "string" | "number" | "boolean" | "date" | "time" | "phone" | "email" | "currency" | "choice";
|
|
2
|
+
export type VoicePathwaySlot = {
|
|
3
|
+
id: string;
|
|
4
|
+
type: VoicePathwaySlotType;
|
|
5
|
+
prompt: string;
|
|
6
|
+
required?: boolean;
|
|
7
|
+
choices?: string[];
|
|
8
|
+
description?: string;
|
|
9
|
+
validation?: {
|
|
10
|
+
minLength?: number;
|
|
11
|
+
maxLength?: number;
|
|
12
|
+
pattern?: string;
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type VoicePathwayCondition = {
|
|
18
|
+
kind: "slot-equals";
|
|
19
|
+
slotId: string;
|
|
20
|
+
value: string | number | boolean;
|
|
21
|
+
} | {
|
|
22
|
+
kind: "slot-filled";
|
|
23
|
+
slotId: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "slot-matches";
|
|
26
|
+
slotId: string;
|
|
27
|
+
pattern: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "always";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "fallback";
|
|
32
|
+
};
|
|
33
|
+
export type VoicePathwayAction = {
|
|
34
|
+
kind: "say";
|
|
35
|
+
text: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: "collect-slot";
|
|
38
|
+
slotId: string;
|
|
39
|
+
} | {
|
|
40
|
+
kind: "call-tool";
|
|
41
|
+
toolId: string;
|
|
42
|
+
argsFromSlots?: string[];
|
|
43
|
+
} | {
|
|
44
|
+
kind: "set-slot";
|
|
45
|
+
slotId: string;
|
|
46
|
+
valueExpression: string;
|
|
47
|
+
} | {
|
|
48
|
+
kind: "transfer";
|
|
49
|
+
destination: string;
|
|
50
|
+
} | {
|
|
51
|
+
kind: "end-call";
|
|
52
|
+
reason?: string;
|
|
53
|
+
};
|
|
54
|
+
export type VoicePathwayTransition = {
|
|
55
|
+
to: string;
|
|
56
|
+
condition: VoicePathwayCondition;
|
|
57
|
+
description?: string;
|
|
58
|
+
};
|
|
59
|
+
export type VoicePathwayState = {
|
|
60
|
+
id: string;
|
|
61
|
+
label: string;
|
|
62
|
+
kind?: "entry" | "collect" | "branch" | "action" | "terminal";
|
|
63
|
+
description?: string;
|
|
64
|
+
systemNote?: string;
|
|
65
|
+
actions?: VoicePathwayAction[];
|
|
66
|
+
transitions: VoicePathwayTransition[];
|
|
67
|
+
};
|
|
68
|
+
export type VoicePathway = {
|
|
69
|
+
id: string;
|
|
70
|
+
label: string;
|
|
71
|
+
entryStateId: string;
|
|
72
|
+
states: VoicePathwayState[];
|
|
73
|
+
slots: VoicePathwaySlot[];
|
|
74
|
+
tools?: {
|
|
75
|
+
id: string;
|
|
76
|
+
description: string;
|
|
77
|
+
}[];
|
|
78
|
+
metadata?: Record<string, string>;
|
|
79
|
+
};
|
|
80
|
+
export type VoicePathwayValidationIssue = {
|
|
81
|
+
severity: "error" | "warning";
|
|
82
|
+
code: "duplicate-state" | "duplicate-slot" | "unknown-entry" | "unreachable-state" | "missing-transition-target" | "missing-slot-ref" | "no-terminal-reachable" | "fallback-not-last" | "duplicate-transition";
|
|
83
|
+
message: string;
|
|
84
|
+
stateId?: string;
|
|
85
|
+
slotId?: string;
|
|
86
|
+
};
|
|
87
|
+
export type VoicePathwayValidationReport = {
|
|
88
|
+
valid: boolean;
|
|
89
|
+
issues: VoicePathwayValidationIssue[];
|
|
90
|
+
reachableStates: string[];
|
|
91
|
+
};
|
|
92
|
+
export declare const validateVoicePathway: (pathway: VoicePathway) => VoicePathwayValidationReport;
|
|
93
|
+
export declare const findVoicePathwayState: (pathway: VoicePathway, id: string) => VoicePathwayState | null;
|
|
94
|
+
export declare const findVoicePathwaySlot: (pathway: VoicePathway, id: string) => VoicePathwaySlot | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type VoicePathway } from "./pathway";
|
|
2
|
+
export type VoicePathwayCompilerToolDefinition = {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: Record<string, {
|
|
8
|
+
type: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
enum?: string[];
|
|
11
|
+
}>;
|
|
12
|
+
required: string[];
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type VoicePathwayCompiledAssistant = {
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
tools: VoicePathwayCompilerToolDefinition[];
|
|
18
|
+
initialPrompt?: string;
|
|
19
|
+
metadata: {
|
|
20
|
+
pathwayId: string;
|
|
21
|
+
pathwayLabel: string;
|
|
22
|
+
entryStateId: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export type CompileVoicePathwayOptions = {
|
|
26
|
+
pathway: VoicePathway;
|
|
27
|
+
introduction?: string;
|
|
28
|
+
fallbackBehavior?: "stay" | "end-call" | "transfer";
|
|
29
|
+
toolNamePrefix?: string;
|
|
30
|
+
};
|
|
31
|
+
export declare const compileVoicePathwayToAssistant: (options: CompileVoicePathwayOptions) => VoicePathwayCompiledAssistant;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type VoicePathway, type VoicePathwayAction } from "./pathway";
|
|
2
|
+
export type VoicePathwaySlotValue = string | number | boolean | null;
|
|
3
|
+
export type VoicePathwayRuntimeStatus = "ready" | "awaiting-slot" | "branching" | "ended" | "errored";
|
|
4
|
+
export type VoicePathwayRuntimeState = {
|
|
5
|
+
currentStateId: string;
|
|
6
|
+
status: VoicePathwayRuntimeStatus;
|
|
7
|
+
slots: Record<string, VoicePathwaySlotValue>;
|
|
8
|
+
awaitingSlotId: string | null;
|
|
9
|
+
history: {
|
|
10
|
+
stateId: string;
|
|
11
|
+
at: number;
|
|
12
|
+
}[];
|
|
13
|
+
pendingActions: VoicePathwayAction[];
|
|
14
|
+
lastError?: string;
|
|
15
|
+
endedReason?: string;
|
|
16
|
+
};
|
|
17
|
+
export type VoicePathwayToolCall = {
|
|
18
|
+
toolId: string;
|
|
19
|
+
args: Record<string, VoicePathwaySlotValue>;
|
|
20
|
+
};
|
|
21
|
+
export type VoicePathwayRuntimeEvent = {
|
|
22
|
+
type: "say";
|
|
23
|
+
text: string;
|
|
24
|
+
} | {
|
|
25
|
+
type: "ask-slot";
|
|
26
|
+
slotId: string;
|
|
27
|
+
prompt: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "tool-call";
|
|
30
|
+
call: VoicePathwayToolCall;
|
|
31
|
+
} | {
|
|
32
|
+
type: "transfer";
|
|
33
|
+
destination: string;
|
|
34
|
+
} | {
|
|
35
|
+
type: "end-call";
|
|
36
|
+
reason?: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: "state-entered";
|
|
39
|
+
stateId: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: "errored";
|
|
42
|
+
message: string;
|
|
43
|
+
};
|
|
44
|
+
export type CreateVoicePathwayRuntimeOptions = {
|
|
45
|
+
pathway: VoicePathway;
|
|
46
|
+
initialSlots?: Record<string, VoicePathwaySlotValue>;
|
|
47
|
+
now?: () => number;
|
|
48
|
+
skipValidation?: boolean;
|
|
49
|
+
};
|
|
50
|
+
export declare const createVoicePathwayRuntime: (options: CreateVoicePathwayRuntimeOptions) => {
|
|
51
|
+
fillSlot: (slotId: string, value: VoicePathwaySlotValue) => void;
|
|
52
|
+
getState: () => VoicePathwayRuntimeState;
|
|
53
|
+
start: () => void;
|
|
54
|
+
subscribe(listener: (event: VoicePathwayRuntimeEvent) => void): () => void;
|
|
55
|
+
tryTransition: () => void;
|
|
56
|
+
};
|
|
57
|
+
export type VoicePathwayRuntime = ReturnType<typeof createVoicePathwayRuntime>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { VoicePathwaySlot, VoicePathwaySlotType } from "./pathway";
|
|
2
|
+
import type { VoicePathwaySlotValue } from "./pathwayRuntime";
|
|
3
|
+
export type VoicePathwaySlotParseResult = {
|
|
4
|
+
ok: true;
|
|
5
|
+
value: VoicePathwaySlotValue;
|
|
6
|
+
normalized: string;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
reason: "empty" | "type-mismatch" | "out-of-range" | "no-match";
|
|
10
|
+
hint?: string;
|
|
11
|
+
};
|
|
12
|
+
export type VoicePathwaySlotParser = (raw: string, slot: VoicePathwaySlot) => VoicePathwaySlotParseResult;
|
|
13
|
+
export declare const DEFAULT_VOICE_PATHWAY_SLOT_PARSERS: Record<VoicePathwaySlotType, VoicePathwaySlotParser>;
|
|
14
|
+
export type CreateVoicePathwaySlotCollectorOptions = {
|
|
15
|
+
parsers?: Partial<Record<VoicePathwaySlotType, VoicePathwaySlotParser>>;
|
|
16
|
+
maxAttemptsPerSlot?: number;
|
|
17
|
+
};
|
|
18
|
+
export type VoicePathwaySlotCollectorAttempt = {
|
|
19
|
+
slotId: string;
|
|
20
|
+
raw: string;
|
|
21
|
+
result: VoicePathwaySlotParseResult;
|
|
22
|
+
attempt: number;
|
|
23
|
+
};
|
|
24
|
+
export declare const createVoicePathwaySlotCollector: (options?: CreateVoicePathwaySlotCollectorOptions) => {
|
|
25
|
+
attemptsExceeded: (slotId: string) => boolean;
|
|
26
|
+
interpret: (slot: VoicePathwaySlot, raw: string) => VoicePathwaySlotCollectorAttempt;
|
|
27
|
+
reset: (slotId?: string) => void;
|
|
28
|
+
};
|
|
29
|
+
export type VoicePathwaySlotCollector = ReturnType<typeof createVoicePathwaySlotCollector>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { VoicePathway } from "./pathway";
|
|
2
|
+
export declare const renderVoicePathwayMermaid: (pathway: VoicePathway) => string;
|
|
3
|
+
export declare const renderVoicePathwayText: (pathway: VoicePathway) => string;
|
|
4
|
+
export type VoicePathwayVisualization = {
|
|
5
|
+
mermaid: string;
|
|
6
|
+
text: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const visualizeVoicePathway: (pathway: VoicePathway) => VoicePathwayVisualization;
|