@voicelayer/sdk 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/index.js +42 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +898 -809
- package/dist/runtime/text-session.js +78 -15
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -495,9 +495,11 @@ var init_agents = __esm({
|
|
|
495
495
|
});
|
|
496
496
|
ProcessSchemaDTO = z.object({
|
|
497
497
|
id: z.string().min(1).max(64),
|
|
498
|
-
// System-prompt fragments compiled from say/confirm nodes. The SDK bridge
|
|
499
|
-
// composes these into the agent's prompt.
|
|
500
|
-
|
|
498
|
+
// System-prompt fragments compiled from say/confirm/llm nodes. The SDK bridge
|
|
499
|
+
// composes these into the agent's prompt. An AI step's instructions are a
|
|
500
|
+
// system prompt in their own right, so the cap is generous (2,000 refused real
|
|
501
|
+
// flows — G-27); publish checks it (validateFlowGraph → compiledSchemaIssues).
|
|
502
|
+
prompts: z.array(z.object({ id: z.string().min(1).max(128), text: z.string().max(16e3) })).max(64).optional(),
|
|
501
503
|
fields: z.array(ProcessFieldDTO).max(64),
|
|
502
504
|
completionGate: z.object({
|
|
503
505
|
requiredFields: z.array(z.string()).max(64),
|
|
@@ -1872,28 +1874,17 @@ var init_flow_ports = __esm({
|
|
|
1872
1874
|
INCOMPLETE_PORT_ID = "incomplete";
|
|
1873
1875
|
}
|
|
1874
1876
|
});
|
|
1877
|
+
|
|
1878
|
+
// ../contracts/src/flow-edges.ts
|
|
1875
1879
|
function isLabelOnlyEdge(edge, sourceType) {
|
|
1876
1880
|
if (!CONVERSATIONAL_NODE_TYPES.has(sourceType)) return false;
|
|
1877
1881
|
if (edge.sourceHandle) return false;
|
|
1878
1882
|
if (typeof edge.condition === "string" && edge.condition.trim()) return false;
|
|
1879
1883
|
return typeof edge.label === "string" && edge.label.trim().length > 0;
|
|
1880
1884
|
}
|
|
1881
|
-
var
|
|
1882
|
-
var
|
|
1883
|
-
"../contracts/src/flow-
|
|
1884
|
-
init_flow_ports();
|
|
1885
|
-
FlowIssueSeverity = z.enum(["error", "warning"]);
|
|
1886
|
-
FlowIssue = z.object({
|
|
1887
|
-
code: z.string().max(64),
|
|
1888
|
-
severity: FlowIssueSeverity,
|
|
1889
|
-
message: z.string().max(500),
|
|
1890
|
-
nodeId: z.string().max(128).optional(),
|
|
1891
|
-
edgeId: z.string().max(128).optional()
|
|
1892
|
-
});
|
|
1893
|
-
z.object({
|
|
1894
|
-
valid: z.boolean(),
|
|
1895
|
-
issues: z.array(FlowIssue).max(256)
|
|
1896
|
-
});
|
|
1885
|
+
var CONVERSATIONAL_NODE_TYPES;
|
|
1886
|
+
var init_flow_edges = __esm({
|
|
1887
|
+
"../contracts/src/flow-edges.ts"() {
|
|
1897
1888
|
CONVERSATIONAL_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
1898
1889
|
"llm",
|
|
1899
1890
|
"ask",
|
|
@@ -1902,838 +1893,909 @@ var init_flow_validate = __esm({
|
|
|
1902
1893
|
}
|
|
1903
1894
|
});
|
|
1904
1895
|
|
|
1905
|
-
// ../contracts/src/flow-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
ComplianceDecision = z.enum(["allowed", "blocked"]);
|
|
1915
|
-
ComplianceConfig = z.object({
|
|
1916
|
-
// Local calling window for the CALLED party — TCPA default 8am–9pm.
|
|
1917
|
-
quietHoursStartHour: z.number().int().min(0).max(23).default(8),
|
|
1918
|
-
quietHoursEndHour: z.number().int().min(1).max(24).default(21),
|
|
1919
|
-
enforceDnc: z.boolean().default(true),
|
|
1920
|
-
enforceQuietHours: z.boolean().default(true),
|
|
1921
|
-
// Max dial attempts to one number per rolling 24h. 0 = unlimited.
|
|
1922
|
-
maxAttemptsPerDay: z.number().int().min(0).default(0),
|
|
1923
|
-
// Require a recorded consent flag before dialing.
|
|
1924
|
-
requireConsent: z.boolean().default(false),
|
|
1925
|
-
// Fallback IANA tz when the number's region can't be resolved.
|
|
1926
|
-
fallbackTimeZone: z.string().min(1).default("America/New_York")
|
|
1927
|
-
});
|
|
1928
|
-
z.object({
|
|
1929
|
-
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
1930
|
-
// ISO instant the check is evaluated at (defaults to now server-side).
|
|
1931
|
-
now: z.string().datetime().optional(),
|
|
1932
|
-
// Override the resolved timezone (else derived from the number's area code).
|
|
1933
|
-
timeZone: z.string().optional(),
|
|
1934
|
-
// Recent attempt count in the last 24h (for the frequency cap), if known.
|
|
1935
|
-
recentAttempts: z.number().int().min(0).optional(),
|
|
1936
|
-
// Whether consent is on file (for requireConsent).
|
|
1937
|
-
hasConsent: z.boolean().optional()
|
|
1938
|
-
});
|
|
1939
|
-
z.object({
|
|
1940
|
-
decision: ComplianceDecision,
|
|
1941
|
-
// The first rule that blocked, if any.
|
|
1942
|
-
rule: ComplianceRule.nullable(),
|
|
1943
|
-
reason: z.string().nullable()
|
|
1944
|
-
});
|
|
1945
|
-
z.object({
|
|
1946
|
-
id: z.string().uuid(),
|
|
1947
|
-
projectId: z.string().min(1),
|
|
1948
|
-
phoneE164: z.string(),
|
|
1949
|
-
reason: z.string().nullable(),
|
|
1950
|
-
source: z.string().nullable(),
|
|
1951
|
-
createdAt: z.string().datetime()
|
|
1952
|
-
});
|
|
1953
|
-
z.object({
|
|
1954
|
-
id: z.string().uuid(),
|
|
1955
|
-
projectId: z.string().min(1),
|
|
1956
|
-
phoneE164: z.string(),
|
|
1957
|
-
decision: ComplianceDecision,
|
|
1958
|
-
rule: ComplianceRule.nullable(),
|
|
1959
|
-
reason: z.string().nullable(),
|
|
1960
|
-
checkedAt: z.string().datetime()
|
|
1961
|
-
});
|
|
1962
|
-
z.object({
|
|
1963
|
-
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
1964
|
-
reason: z.string().max(500).optional(),
|
|
1965
|
-
source: z.string().max(120).optional()
|
|
1966
|
-
});
|
|
1967
|
-
ComplianceConfig.partial();
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
|
|
1971
|
-
// ../contracts/src/sms-keywords.ts
|
|
1972
|
-
var init_sms_keywords = __esm({
|
|
1973
|
-
"../contracts/src/sms-keywords.ts"() {
|
|
1896
|
+
// ../contracts/src/flow-enrichment.ts
|
|
1897
|
+
function planEnrichment(graph) {
|
|
1898
|
+
const ids = graph.nodes.map((n) => n.id);
|
|
1899
|
+
const indeg = new Map(ids.map((id) => [id, 0]));
|
|
1900
|
+
const adj = new Map(ids.map((id) => [id, []]));
|
|
1901
|
+
for (const edge of graph.edges) {
|
|
1902
|
+
if (!adj.has(edge.source) || !indeg.has(edge.target)) continue;
|
|
1903
|
+
adj.get(edge.source).push(edge.target);
|
|
1904
|
+
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
1974
1905
|
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
}
|
|
1985
|
-
z.object({
|
|
1986
|
-
recordingEnabled: z.boolean().optional(),
|
|
1987
|
-
consentAnnouncement: z.string().max(1e3).nullable().optional()
|
|
1988
|
-
});
|
|
1906
|
+
const queue = ids.filter((id) => (indeg.get(id) ?? 0) === 0);
|
|
1907
|
+
const order = [];
|
|
1908
|
+
while (queue.length > 0) {
|
|
1909
|
+
const id = queue.shift();
|
|
1910
|
+
order.push(id);
|
|
1911
|
+
for (const next of adj.get(id) ?? []) {
|
|
1912
|
+
const left = (indeg.get(next) ?? 0) - 1;
|
|
1913
|
+
indeg.set(next, left);
|
|
1914
|
+
if (left === 0) queue.push(next);
|
|
1915
|
+
}
|
|
1989
1916
|
}
|
|
1990
|
-
}
|
|
1991
|
-
|
|
1992
|
-
var
|
|
1993
|
-
"../contracts/src/
|
|
1994
|
-
OutboundAmdMode = z.enum(["off", "detect", "detectMessageEnd"]);
|
|
1995
|
-
z.object({
|
|
1996
|
-
// Callee in E.164 (e.g. +14155551234). call-router re-validates strictly.
|
|
1997
|
-
to: z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164, e.g. +14155551234"),
|
|
1998
|
-
// Caller-id phone number (must belong to the calling project).
|
|
1999
|
-
phoneNumberId: z.string().min(1),
|
|
2000
|
-
// Optional agent override; falls back to the number's bound agent.
|
|
2001
|
-
agentId: z.string().min(1).optional(),
|
|
2002
|
-
// Template/context variables forwarded to the agent as dispatch metadata.
|
|
2003
|
-
variables: z.record(z.string(), z.string()).optional(),
|
|
2004
|
-
// Per-call AMD override; takes precedence over the agent's stored outbound.amd.
|
|
2005
|
-
amd: OutboundAmdMode.optional()
|
|
2006
|
-
});
|
|
1917
|
+
return { order, acyclic: order.length === ids.length };
|
|
1918
|
+
}
|
|
1919
|
+
var init_flow_enrichment = __esm({
|
|
1920
|
+
"../contracts/src/flow-enrichment.ts"() {
|
|
2007
1921
|
}
|
|
2008
1922
|
});
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
metadata: z.record(z.unknown()).optional(),
|
|
2048
|
-
expiresAt: z.string().datetime().optional()
|
|
2049
|
-
});
|
|
2050
|
-
z.object({
|
|
2051
|
-
name: z.string().trim().min(1).max(128).optional(),
|
|
2052
|
-
// New secret material — re-encrypted at rest; the old envelope is replaced.
|
|
2053
|
-
secret: z.string().min(1).max(8192).optional(),
|
|
2054
|
-
// Merged over the existing metadata (top-level keys win).
|
|
2055
|
-
metadata: z.record(z.unknown()).optional()
|
|
2056
|
-
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
2057
|
-
message: "nothing to update"
|
|
2058
|
-
});
|
|
2059
|
-
byokProviders().map((p) => ({
|
|
2060
|
-
id: p.id,
|
|
2061
|
-
label: p.label,
|
|
2062
|
-
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
2063
|
-
supportsBaseUrl: p.supportsBaseUrl
|
|
2064
|
-
}));
|
|
1923
|
+
|
|
1924
|
+
// ../contracts/src/flow-compile.ts
|
|
1925
|
+
function compileConversationFlow(graph, id = "flow") {
|
|
1926
|
+
const fields = [];
|
|
1927
|
+
const prompts = [];
|
|
1928
|
+
const triggers = [];
|
|
1929
|
+
let gateRequired = null;
|
|
1930
|
+
for (const node of graph.nodes) {
|
|
1931
|
+
const data = node.data;
|
|
1932
|
+
switch (node.type) {
|
|
1933
|
+
case "ask": {
|
|
1934
|
+
const name = typeof data.slot === "string" ? data.slot : node.id;
|
|
1935
|
+
const required = data.required !== false;
|
|
1936
|
+
fields.push({
|
|
1937
|
+
name,
|
|
1938
|
+
required,
|
|
1939
|
+
...typeof data.ask === "string" ? { prompt: data.ask } : {}
|
|
1940
|
+
});
|
|
1941
|
+
break;
|
|
1942
|
+
}
|
|
1943
|
+
case "say":
|
|
1944
|
+
case "confirm": {
|
|
1945
|
+
if (typeof data.text === "string") prompts.push({ id: node.id, text: data.text });
|
|
1946
|
+
break;
|
|
1947
|
+
}
|
|
1948
|
+
case "trigger": {
|
|
1949
|
+
const on = typeof data.on === "string" ? data.on : "";
|
|
1950
|
+
const then = typeof data.then === "string" ? data.then : "handoff";
|
|
1951
|
+
if (on) triggers.push({ on, then });
|
|
1952
|
+
break;
|
|
1953
|
+
}
|
|
1954
|
+
case "gate": {
|
|
1955
|
+
if (Array.isArray(data.requiredFields)) {
|
|
1956
|
+
gateRequired = data.requiredFields.filter((x) => typeof x === "string");
|
|
1957
|
+
}
|
|
1958
|
+
break;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
2065
1961
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
1962
|
+
const requiredFields = gateRequired ?? fields.filter((f) => f.required).map((f) => f.name);
|
|
1963
|
+
return { id, fields, completionGate: { requiredFields }, triggers, prompts };
|
|
1964
|
+
}
|
|
1965
|
+
function asFieldType(value) {
|
|
1966
|
+
return typeof value === "string" && FIELD_TYPES.includes(value) ? value : "string";
|
|
1967
|
+
}
|
|
1968
|
+
function asHistoryPolicy(value) {
|
|
1969
|
+
return typeof value === "string" && HISTORY_POLICIES.has(value) ? value : "full";
|
|
1970
|
+
}
|
|
1971
|
+
function asAdherence(value) {
|
|
1972
|
+
return typeof value === "string" && ADHERENCE_MODES.has(value) ? value : "off";
|
|
1973
|
+
}
|
|
1974
|
+
function branchesFor(data) {
|
|
1975
|
+
return Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
1976
|
+
id: b.id,
|
|
1977
|
+
label: b.label,
|
|
1978
|
+
...typeof b.when === "string" && b.when.trim() ? { when: b.when } : {},
|
|
1979
|
+
...typeof b.digit === "string" && b.digit.trim() ? { digit: b.digit.trim() } : {},
|
|
1980
|
+
...typeof b.description === "string" && b.description.trim() ? { description: b.description } : {}
|
|
1981
|
+
})) : [];
|
|
1982
|
+
}
|
|
1983
|
+
function nodeConfigFor(node) {
|
|
1984
|
+
const data = node.data;
|
|
1985
|
+
switch (node.type) {
|
|
1986
|
+
case "start":
|
|
1987
|
+
return {};
|
|
1988
|
+
case "say":
|
|
1989
|
+
case "confirm":
|
|
1990
|
+
return { text: typeof data.text === "string" ? data.text : "" };
|
|
1991
|
+
case "ask":
|
|
1992
|
+
return {
|
|
1993
|
+
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
1994
|
+
required: data.required !== false,
|
|
1995
|
+
type: asFieldType(data.type),
|
|
1996
|
+
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
1997
|
+
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
1998
|
+
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
1999
|
+
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2000
|
+
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2001
|
+
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2002
|
+
};
|
|
2003
|
+
case "llm":
|
|
2004
|
+
return {
|
|
2005
|
+
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
2006
|
+
// Chat-hub valve: a resident conversational node (entry/return point)
|
|
2007
|
+
// that routes to task subgraphs on its conditional edges and absorbs
|
|
2008
|
+
// off-script turns in place instead of dead-ending. See the interpreter.
|
|
2009
|
+
hub: data.hub === true,
|
|
2010
|
+
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2011
|
+
// Drift-gate (supervisor-lite): verify the reply still localizes to this
|
|
2012
|
+
// node. enforce → reject-and-regenerate, then speak `fallback` verbatim.
|
|
2013
|
+
adherence: asAdherence(data.adherence),
|
|
2014
|
+
...typeof data.fallback === "string" ? { fallback: data.fallback } : {}
|
|
2015
|
+
};
|
|
2016
|
+
case "tool": {
|
|
2017
|
+
const url = typeof data.url === "string" ? data.url : "";
|
|
2018
|
+
const rawName = typeof data.name === "string" ? data.name : "";
|
|
2019
|
+
const name = /^[a-z][a-z0-9_]*$/.test(rawName) ? rawName : `tool_${node.id.replace(/[^a-z0-9]/gi, "").slice(0, 16).toLowerCase() || "fn"}`;
|
|
2020
|
+
const input = {};
|
|
2021
|
+
if (data.input && typeof data.input === "object") {
|
|
2022
|
+
for (const [key, val] of Object.entries(data.input)) {
|
|
2023
|
+
if (val === "string" || val === "number" || val === "boolean") input[key] = val;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const headers = {};
|
|
2027
|
+
if (data.headers && typeof data.headers === "object") {
|
|
2028
|
+
for (const [key, val] of Object.entries(data.headers)) {
|
|
2029
|
+
if (typeof val === "string") headers[key] = val;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
return {
|
|
2033
|
+
name,
|
|
2034
|
+
description: typeof data.description === "string" && data.description ? data.description : name,
|
|
2035
|
+
input,
|
|
2036
|
+
url,
|
|
2037
|
+
method: HTTP_METHODS.has(data.method) ? data.method : "POST",
|
|
2038
|
+
...Object.keys(headers).length ? { headers } : {},
|
|
2039
|
+
auth: data.auth === "connection" ? "connection" : "none",
|
|
2040
|
+
...typeof data.connectionRef === "string" ? { connectionRef: data.connectionRef } : {},
|
|
2041
|
+
// Registry reference: run the project HTTP tool with this NAME via the
|
|
2042
|
+
// server-side proxy instead of the inline url/method/auth above.
|
|
2043
|
+
...typeof data.toolRef === "string" && data.toolRef ? { toolRef: data.toolRef } : {},
|
|
2044
|
+
// Promote result fields into NAMED slots on success:
|
|
2045
|
+
// { slotName: "data.path" }. Later nodes then read {{ slotName }}
|
|
2046
|
+
// directly instead of {{ tool.<nodeId>.data.path }} via a set node.
|
|
2047
|
+
...(() => {
|
|
2048
|
+
if (!data.outputAs || typeof data.outputAs !== "object") return {};
|
|
2049
|
+
const outputAs = {};
|
|
2050
|
+
for (const [slot, path] of Object.entries(data.outputAs)) {
|
|
2051
|
+
if (typeof path === "string" && path.trim() && /^[a-zA-Z_][\w.]*$/.test(slot)) {
|
|
2052
|
+
outputAs[slot] = path.trim();
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
return Object.keys(outputAs).length ? { outputAs } : {};
|
|
2056
|
+
})()
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
case "gate":
|
|
2060
|
+
return {
|
|
2061
|
+
requiredFields: Array.isArray(data.requiredFields) ? data.requiredFields.filter((x) => typeof x === "string") : []
|
|
2062
|
+
};
|
|
2063
|
+
case "handoff":
|
|
2064
|
+
return {
|
|
2065
|
+
...typeof data.to === "string" ? { to: data.to } : {},
|
|
2066
|
+
...typeof data.fallback === "string" ? { fallback: data.fallback } : {},
|
|
2067
|
+
...typeof data.on === "string" ? { on: data.on } : {},
|
|
2068
|
+
// Per-node warm (conference brief) / cold (blind REFER) override.
|
|
2069
|
+
...data.mode === "warm" || data.mode === "cold" ? { mode: data.mode } : {},
|
|
2070
|
+
// What the joining human HEARS (warm mode speaks it verbatim once they
|
|
2071
|
+
// answer). {{ }}-interpolated at runtime against the slot scope.
|
|
2072
|
+
...typeof data.briefing === "string" && data.briefing.trim() ? { briefing: data.briefing } : {}
|
|
2073
|
+
};
|
|
2074
|
+
case "end":
|
|
2075
|
+
return {
|
|
2076
|
+
...typeof data.on === "string" ? { on: data.on } : {},
|
|
2077
|
+
...typeof data.voicemail === "boolean" ? { voicemail: data.voicemail } : {},
|
|
2078
|
+
// Optional closing line spoken before hang-up (interpolated at runtime).
|
|
2079
|
+
...typeof data.endMessage === "string" ? { endMessage: data.endMessage } : {}
|
|
2080
|
+
};
|
|
2081
|
+
case "trigger":
|
|
2082
|
+
return {
|
|
2083
|
+
on: typeof data.on === "string" ? data.on : "",
|
|
2084
|
+
then: typeof data.then === "string" ? data.then : "handoff",
|
|
2085
|
+
...typeof data.onlyWhile === "string" ? { onlyWhile: data.onlyWhile } : {}
|
|
2086
|
+
};
|
|
2087
|
+
case "decision":
|
|
2088
|
+
return {
|
|
2089
|
+
...typeof data.condition === "string" ? { condition: data.condition } : {},
|
|
2090
|
+
branches: branchesFor(data)
|
|
2091
|
+
};
|
|
2092
|
+
case "playbook":
|
|
2093
|
+
return {
|
|
2094
|
+
...typeof data.goal === "string" ? { goal: data.goal } : {},
|
|
2095
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2096
|
+
// Optional reference to a reusable library playbook. playbookId is the
|
|
2097
|
+
// rename-safe key (pinned at deploy); playbookRef (name) remains for
|
|
2098
|
+
// display + legacy live resolution.
|
|
2099
|
+
...typeof data.playbookRef === "string" ? { playbookRef: data.playbookRef } : {},
|
|
2100
|
+
...typeof data.playbookId === "string" && data.playbookId ? { playbookId: data.playbookId } : {},
|
|
2101
|
+
requiredFields: Array.isArray(data.requiredFields) ? data.requiredFields.filter((x) => typeof x === "string") : [],
|
|
2102
|
+
// Agent loop: scoped registry-tool names + explicit opt-in + model knobs.
|
|
2103
|
+
// With tools (or agentLoop:true) the runtime swaps the plain conversational
|
|
2104
|
+
// turn for the LLM-with-tools runner and the structured exit tool.
|
|
2105
|
+
...Array.isArray(data.tools) ? { tools: data.tools.filter((x) => typeof x === "string" && x.length > 0) } : {},
|
|
2106
|
+
...data.agentLoop === true ? { agentLoop: true } : {},
|
|
2107
|
+
...typeof data.model === "string" && data.model ? { model: data.model } : {},
|
|
2108
|
+
...typeof data.temperature === "number" ? { temperature: data.temperature } : {},
|
|
2109
|
+
// Spoken close when the turn budget exhausts with required fields missing
|
|
2110
|
+
// and no `incomplete` port is wired (the gate never silently advances).
|
|
2111
|
+
...typeof data.incompleteMessage === "string" && data.incompleteMessage ? { incompleteMessage: data.incompleteMessage } : {},
|
|
2112
|
+
// Turn policy: agent-generated speech is interruptible by default;
|
|
2113
|
+
// false locks it (verbatim lines are always locked regardless).
|
|
2114
|
+
...data.interruptible === false ? { interruptible: false } : {},
|
|
2115
|
+
branches: branchesFor(data)
|
|
2116
|
+
};
|
|
2117
|
+
case "menu":
|
|
2118
|
+
return {
|
|
2119
|
+
...typeof data.prompt === "string" ? { prompt: data.prompt } : {},
|
|
2120
|
+
branches: branchesFor(data)
|
|
2121
|
+
};
|
|
2122
|
+
case "classify":
|
|
2123
|
+
return {
|
|
2124
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2125
|
+
branches: branchesFor(data)
|
|
2126
|
+
};
|
|
2127
|
+
case "router":
|
|
2128
|
+
return {
|
|
2129
|
+
hub: true,
|
|
2130
|
+
...typeof data.instructions === "string" ? { instructions: data.instructions } : {},
|
|
2131
|
+
branches: Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
2132
|
+
id: b.id,
|
|
2133
|
+
label: b.label,
|
|
2134
|
+
...typeof b.description === "string" && b.description.trim() ? { description: b.description } : {}
|
|
2135
|
+
})) : []
|
|
2136
|
+
};
|
|
2137
|
+
case "rag":
|
|
2138
|
+
return {
|
|
2139
|
+
...typeof data.query === "string" ? { query: data.query } : {},
|
|
2140
|
+
...typeof data.topK === "number" ? { topK: data.topK } : {}
|
|
2141
|
+
};
|
|
2142
|
+
case "code":
|
|
2143
|
+
return {
|
|
2144
|
+
...typeof data.expression === "string" ? { expression: data.expression } : {},
|
|
2145
|
+
...typeof data.description === "string" ? { description: data.description } : {}
|
|
2146
|
+
};
|
|
2147
|
+
case "set":
|
|
2148
|
+
return {
|
|
2149
|
+
assignments: Array.isArray(data.assignments) ? data.assignments.filter((a) => a && typeof a.name === "string" && a.name.trim() !== "").map((a) => ({
|
|
2150
|
+
name: a.name,
|
|
2151
|
+
value: typeof a.value === "string" ? a.value : ""
|
|
2152
|
+
})) : []
|
|
2153
|
+
};
|
|
2154
|
+
case "subflow":
|
|
2155
|
+
return {
|
|
2156
|
+
...typeof data.flowRef === "string" ? { flowRef: data.flowRef } : {}
|
|
2157
|
+
};
|
|
2158
|
+
default:
|
|
2159
|
+
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
function compileFlowToProgram(graph, id = "flow") {
|
|
2163
|
+
const stepNodes = graph.nodes.filter(
|
|
2164
|
+
(n) => !CAPABILITY_NODE_TYPES.has(n.type) && !ANNOTATION_NODE_TYPES.has(n.type)
|
|
2165
|
+
);
|
|
2166
|
+
const byId = new Map(stepNodes.map((n) => [n.id, n]));
|
|
2167
|
+
const indeg = new Map(stepNodes.map((n) => [n.id, 0]));
|
|
2168
|
+
const outBySource = /* @__PURE__ */ new Map();
|
|
2169
|
+
const droppedEdges = [];
|
|
2170
|
+
const labelOnlyEdges = [];
|
|
2171
|
+
for (const edge of graph.edges) {
|
|
2172
|
+
const source = byId.get(edge.source);
|
|
2173
|
+
if (!source || !byId.has(edge.target)) {
|
|
2174
|
+
droppedEdges.push(edge.id);
|
|
2175
|
+
continue;
|
|
2176
|
+
}
|
|
2177
|
+
if (isLabelOnlyEdge(edge, source.type)) labelOnlyEdges.push(edge.id);
|
|
2178
|
+
const list = outBySource.get(edge.source) ?? [];
|
|
2179
|
+
list.push({
|
|
2180
|
+
to: edge.target,
|
|
2181
|
+
...edge.condition ? { condition: edge.condition } : {},
|
|
2182
|
+
...edge.sourceHandle ? { sourceHandle: edge.sourceHandle } : {},
|
|
2183
|
+
...edge.label ? { label: edge.label } : {}
|
|
2184
|
+
});
|
|
2185
|
+
outBySource.set(edge.source, list);
|
|
2186
|
+
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2187
|
+
}
|
|
2188
|
+
const nodes = {};
|
|
2189
|
+
const unsupportedNodes = [];
|
|
2190
|
+
for (const node of stepNodes) {
|
|
2191
|
+
nodes[node.id] = {
|
|
2192
|
+
id: node.id,
|
|
2193
|
+
type: node.type,
|
|
2194
|
+
config: nodeConfigFor(node),
|
|
2195
|
+
// Defensive: the FlowProgram schema caps out[] at 64 — slice rather than
|
|
2196
|
+
// letting an over-wired node fail the read-side parse (which silently
|
|
2197
|
+
// degrades the whole call to the generic base prompt).
|
|
2198
|
+
out: (outBySource.get(node.id) ?? []).slice(0, 64)
|
|
2199
|
+
};
|
|
2200
|
+
if (UNSUPPORTED_NODE_TYPES.has(node.type)) unsupportedNodes.push(node.id);
|
|
2201
|
+
}
|
|
2202
|
+
const startNodes = stepNodes.filter((n) => n.type === "start").map((n) => n.id);
|
|
2203
|
+
let entry2 = startNodes.length ? startNodes : stepNodes.filter((n) => (indeg.get(n.id) ?? 0) === 0).map((n) => n.id);
|
|
2204
|
+
if (entry2.length === 0 && stepNodes.length > 0) entry2 = [stepNodes[0].id];
|
|
2205
|
+
const { acyclic } = planEnrichment(graph);
|
|
2206
|
+
const { completionGate } = compileConversationFlow(graph, id);
|
|
2207
|
+
return {
|
|
2208
|
+
v: 1,
|
|
2209
|
+
id: id.slice(0, 64),
|
|
2210
|
+
entry: entry2.slice(0, 64),
|
|
2211
|
+
nodes,
|
|
2212
|
+
completionGate: { requiredFields: completionGate.requiredFields.slice(0, 64) },
|
|
2213
|
+
// Diagnostics arrays are schema-capped at 256 — slice so a pathological
|
|
2214
|
+
// graph can't fail the read-side parse.
|
|
2215
|
+
diagnostics: {
|
|
2216
|
+
acyclic,
|
|
2217
|
+
droppedEdges: droppedEdges.slice(0, 256),
|
|
2218
|
+
unsupportedNodes: unsupportedNodes.slice(0, 256),
|
|
2219
|
+
labelOnlyEdges: labelOnlyEdges.slice(0, 256)
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2224
|
+
var init_flow_compile = __esm({
|
|
2225
|
+
"../contracts/src/flow-compile.ts"() {
|
|
2226
|
+
init_flow_enrichment();
|
|
2227
|
+
init_flow_edges();
|
|
2228
|
+
FIELD_TYPES = [
|
|
2229
|
+
"string",
|
|
2230
|
+
"string[]",
|
|
2231
|
+
"number",
|
|
2232
|
+
"boolean",
|
|
2233
|
+
"datetime",
|
|
2234
|
+
"email",
|
|
2235
|
+
"phone"
|
|
2236
|
+
];
|
|
2237
|
+
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
2238
|
+
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2239
|
+
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2240
|
+
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2241
|
+
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2242
|
+
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
2243
|
+
}
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
// ../contracts/src/flow-compile-check.ts
|
|
2247
|
+
var init_flow_compile_check = __esm({
|
|
2248
|
+
"../contracts/src/flow-compile-check.ts"() {
|
|
2249
|
+
init_agents();
|
|
2250
|
+
init_flow_compile();
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
var FlowIssueSeverity, FlowIssue;
|
|
2254
|
+
var init_flow_validate = __esm({
|
|
2255
|
+
"../contracts/src/flow-validate.ts"() {
|
|
2256
|
+
init_flow_ports();
|
|
2257
|
+
init_flow_edges();
|
|
2258
|
+
init_flow_compile_check();
|
|
2259
|
+
init_flow_edges();
|
|
2260
|
+
FlowIssueSeverity = z.enum(["error", "warning"]);
|
|
2261
|
+
FlowIssue = z.object({
|
|
2262
|
+
code: z.string().max(64),
|
|
2263
|
+
severity: FlowIssueSeverity,
|
|
2264
|
+
message: z.string().max(500),
|
|
2265
|
+
nodeId: z.string().max(128).optional(),
|
|
2266
|
+
edgeId: z.string().max(128).optional()
|
|
2267
|
+
});
|
|
2268
|
+
z.object({
|
|
2269
|
+
valid: z.boolean(),
|
|
2270
|
+
issues: z.array(FlowIssue).max(256)
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
// ../contracts/src/flow-builtins.ts
|
|
2276
|
+
var init_flow_builtins = __esm({
|
|
2277
|
+
"../contracts/src/flow-builtins.ts"() {
|
|
2278
|
+
}
|
|
2279
|
+
});
|
|
2280
|
+
var ComplianceRule, ComplianceDecision, ComplianceConfig;
|
|
2281
|
+
var init_compliance = __esm({
|
|
2282
|
+
"../contracts/src/compliance.ts"() {
|
|
2283
|
+
ComplianceRule = z.enum(["dnc", "consent", "quiet_hours", "frequency_cap"]);
|
|
2284
|
+
ComplianceDecision = z.enum(["allowed", "blocked"]);
|
|
2285
|
+
ComplianceConfig = z.object({
|
|
2286
|
+
// Local calling window for the CALLED party — TCPA default 8am–9pm.
|
|
2287
|
+
quietHoursStartHour: z.number().int().min(0).max(23).default(8),
|
|
2288
|
+
quietHoursEndHour: z.number().int().min(1).max(24).default(21),
|
|
2289
|
+
enforceDnc: z.boolean().default(true),
|
|
2290
|
+
enforceQuietHours: z.boolean().default(true),
|
|
2291
|
+
// Max dial attempts to one number per rolling 24h. 0 = unlimited.
|
|
2292
|
+
maxAttemptsPerDay: z.number().int().min(0).default(0),
|
|
2293
|
+
// Require a recorded consent flag before dialing.
|
|
2294
|
+
requireConsent: z.boolean().default(false),
|
|
2295
|
+
// Fallback IANA tz when the number's region can't be resolved.
|
|
2296
|
+
fallbackTimeZone: z.string().min(1).default("America/New_York")
|
|
2297
|
+
});
|
|
2298
|
+
z.object({
|
|
2299
|
+
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
2300
|
+
// ISO instant the check is evaluated at (defaults to now server-side).
|
|
2301
|
+
now: z.string().datetime().optional(),
|
|
2302
|
+
// Override the resolved timezone (else derived from the number's area code).
|
|
2303
|
+
timeZone: z.string().optional(),
|
|
2304
|
+
// Recent attempt count in the last 24h (for the frequency cap), if known.
|
|
2305
|
+
recentAttempts: z.number().int().min(0).optional(),
|
|
2306
|
+
// Whether consent is on file (for requireConsent).
|
|
2307
|
+
hasConsent: z.boolean().optional()
|
|
2308
|
+
});
|
|
2309
|
+
z.object({
|
|
2310
|
+
decision: ComplianceDecision,
|
|
2311
|
+
// The first rule that blocked, if any.
|
|
2312
|
+
rule: ComplianceRule.nullable(),
|
|
2313
|
+
reason: z.string().nullable()
|
|
2314
|
+
});
|
|
2315
|
+
z.object({
|
|
2316
|
+
id: z.string().uuid(),
|
|
2317
|
+
projectId: z.string().min(1),
|
|
2318
|
+
phoneE164: z.string(),
|
|
2319
|
+
reason: z.string().nullable(),
|
|
2320
|
+
source: z.string().nullable(),
|
|
2321
|
+
createdAt: z.string().datetime()
|
|
2106
2322
|
});
|
|
2107
2323
|
z.object({
|
|
2108
|
-
|
|
2109
|
-
|
|
2324
|
+
id: z.string().uuid(),
|
|
2325
|
+
projectId: z.string().min(1),
|
|
2326
|
+
phoneE164: z.string(),
|
|
2327
|
+
decision: ComplianceDecision,
|
|
2328
|
+
rule: ComplianceRule.nullable(),
|
|
2329
|
+
reason: z.string().nullable(),
|
|
2330
|
+
checkedAt: z.string().datetime()
|
|
2331
|
+
});
|
|
2332
|
+
z.object({
|
|
2333
|
+
phoneE164: z.string().regex(/^\+[1-9]\d{1,14}$/, "must be E.164"),
|
|
2334
|
+
reason: z.string().max(500).optional(),
|
|
2335
|
+
source: z.string().max(120).optional()
|
|
2110
2336
|
});
|
|
2337
|
+
ComplianceConfig.partial();
|
|
2111
2338
|
}
|
|
2112
2339
|
});
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
"tiny.cc"
|
|
2128
|
-
];
|
|
2129
|
-
PLACEHOLDER_HOSTS = ["acme.com", "example.com", "example.org", "test.com", "localhost"];
|
|
2130
|
-
PublicUrl = z.string().url().refine((v) => v.startsWith("https://"), "must be https").refine((v) => {
|
|
2131
|
-
try {
|
|
2132
|
-
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2133
|
-
return !SHORTENER_HOSTS.includes(host);
|
|
2134
|
-
} catch {
|
|
2135
|
-
return false;
|
|
2136
|
-
}
|
|
2137
|
-
}, "public URL shorteners are rejected by carriers \u2014 use your own domain").refine((v) => {
|
|
2138
|
-
try {
|
|
2139
|
-
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2140
|
-
return !PLACEHOLDER_HOSTS.some((p) => host === p || host.endsWith(`.${p}`));
|
|
2141
|
-
} catch {
|
|
2142
|
-
return false;
|
|
2143
|
-
}
|
|
2144
|
-
}, "placeholder domain \u2014 reviewers will follow this link and reject the campaign");
|
|
2145
|
-
A2pBusinessType = z.enum([
|
|
2146
|
-
"Sole Proprietorship",
|
|
2147
|
-
"Partnership",
|
|
2148
|
-
"Corporation",
|
|
2149
|
-
"Co-operative",
|
|
2150
|
-
"Limited Liability Corporation",
|
|
2151
|
-
"Non-profit Corporation"
|
|
2152
|
-
]);
|
|
2153
|
-
A2pBusinessInfo = z.object({
|
|
2154
|
-
legalName: z.string().trim().min(2).max(200),
|
|
2155
|
-
/** EIN (US) or equivalent registration number. */
|
|
2156
|
-
registrationNumber: z.string().trim().min(4).max(50),
|
|
2157
|
-
businessType: A2pBusinessType,
|
|
2158
|
-
/** Publicly reachable production site — not staging, not a 404. */
|
|
2159
|
-
website: PublicUrl,
|
|
2160
|
-
industry: z.string().trim().min(2).max(60),
|
|
2161
|
-
address: z.object({
|
|
2162
|
-
street: z.string().trim().min(2).max(200),
|
|
2163
|
-
city: z.string().trim().min(1).max(100),
|
|
2164
|
-
region: z.string().trim().min(1).max(100),
|
|
2165
|
-
postalCode: z.string().trim().min(2).max(20),
|
|
2166
|
-
isoCountry: z.string().trim().length(2)
|
|
2167
|
-
}),
|
|
2168
|
-
authorizedRep: z.object({
|
|
2169
|
-
firstName: z.string().trim().min(1).max(100),
|
|
2170
|
-
lastName: z.string().trim().min(1).max(100),
|
|
2171
|
-
email: z.string().trim().email(),
|
|
2172
|
-
phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
|
|
2173
|
-
jobTitle: z.string().trim().min(2).max(100)
|
|
2174
|
-
})
|
|
2175
|
-
});
|
|
2176
|
-
A2pOptInType = z.enum(["WEB_FORM", "PAPER_FORM", "VERBAL", "VIA_TEXT", "MOBILE_QR_CODE"]);
|
|
2177
|
-
A2pUseCase = z.enum([
|
|
2178
|
-
"MIXED",
|
|
2179
|
-
"CUSTOMER_CARE",
|
|
2180
|
-
"MARKETING",
|
|
2181
|
-
"ACCOUNT_NOTIFICATION",
|
|
2182
|
-
"2FA",
|
|
2183
|
-
"DELIVERY_NOTIFICATION",
|
|
2184
|
-
"HIGHER_EDUCATION",
|
|
2185
|
-
"POLLING_VOTING",
|
|
2186
|
-
"PUBLIC_SERVICE_ANNOUNCEMENT",
|
|
2187
|
-
"LOW_VOLUME"
|
|
2188
|
-
]);
|
|
2189
|
-
A2pMessagingProfile = z.object({
|
|
2190
|
-
useCase: A2pUseCase,
|
|
2191
|
-
/** Specific, not generic. "We send texts" gets rejected; describe the actual
|
|
2192
|
-
* messages and when they are sent. */
|
|
2193
|
-
description: z.string().trim().min(40).max(4096),
|
|
2194
|
-
/**
|
|
2195
|
-
* The single most-rejected field. Must describe HOW people opt in, state the
|
|
2196
|
-
* message frequency, include the "message and data rates may apply"
|
|
2197
|
-
* disclosure, and link to publicly reachable evidence. Twilio's API bounds it
|
|
2198
|
-
* to 40–2049 characters.
|
|
2199
|
-
*/
|
|
2200
|
-
messageFlow: z.string().trim().min(40).max(2049),
|
|
2201
|
-
optInType: A2pOptInType,
|
|
2202
|
-
/** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
|
|
2203
|
-
* these; anything behind a login fails. */
|
|
2204
|
-
optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
|
|
2205
|
-
/** Real messages the customer will send. Must reflect the declared use case
|
|
2206
|
-
* and carry opt-out language. */
|
|
2207
|
-
messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
|
|
2208
|
-
privacyPolicyUrl: PublicUrl,
|
|
2209
|
-
termsAndConditionsUrl: PublicUrl,
|
|
2210
|
-
hasEmbeddedLinks: z.boolean().default(false),
|
|
2211
|
-
hasEmbeddedPhone: z.boolean().default(false)
|
|
2340
|
+
|
|
2341
|
+
// ../contracts/src/sms-keywords.ts
|
|
2342
|
+
var init_sms_keywords = __esm({
|
|
2343
|
+
"../contracts/src/sms-keywords.ts"() {
|
|
2344
|
+
}
|
|
2345
|
+
});
|
|
2346
|
+
var init_call_recording = __esm({
|
|
2347
|
+
"../contracts/src/call-recording.ts"() {
|
|
2348
|
+
z.object({
|
|
2349
|
+
// When false, the call-event sink skips egress entirely — no recording is made.
|
|
2350
|
+
recordingEnabled: z.boolean().default(true),
|
|
2351
|
+
// Optional consent preamble the agent speaks first on every call. null/empty =
|
|
2352
|
+
// no announcement. Capped so it stays a short notice, not a monologue.
|
|
2353
|
+
consentAnnouncement: z.string().max(1e3).nullable().default(null)
|
|
2212
2354
|
});
|
|
2213
2355
|
z.object({
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
/** Register against Twilio's mock endpoints — exercises the full pipeline
|
|
2217
|
-
* with no fees and no real carrier submission. Used in CI and staging. */
|
|
2218
|
-
mock: z.boolean().default(false)
|
|
2219
|
-
}).superRefine((v, ctx) => {
|
|
2220
|
-
const flow = v.messaging.messageFlow.toLowerCase();
|
|
2221
|
-
if (!/(msg|message)\s*(&|and)\s*data rates/.test(flow)) {
|
|
2222
|
-
ctx.addIssue({
|
|
2223
|
-
code: z.ZodIssueCode.custom,
|
|
2224
|
-
path: ["messaging", "messageFlow"],
|
|
2225
|
-
message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
|
|
2226
|
-
});
|
|
2227
|
-
}
|
|
2228
|
-
if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
|
|
2229
|
-
ctx.addIssue({
|
|
2230
|
-
code: z.ZodIssueCode.custom,
|
|
2231
|
-
path: ["messaging", "messageFlow"],
|
|
2232
|
-
message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
|
|
2233
|
-
});
|
|
2234
|
-
}
|
|
2235
|
-
const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
|
|
2236
|
-
if (!hasOptOut) {
|
|
2237
|
-
ctx.addIssue({
|
|
2238
|
-
code: z.ZodIssueCode.custom,
|
|
2239
|
-
path: ["messaging", "messageSamples"],
|
|
2240
|
-
message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
|
|
2241
|
-
});
|
|
2242
|
-
}
|
|
2356
|
+
recordingEnabled: z.boolean().optional(),
|
|
2357
|
+
consentAnnouncement: z.string().max(1e3).nullable().optional()
|
|
2243
2358
|
});
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
"brand_approved",
|
|
2251
|
-
"brand_failed",
|
|
2252
|
-
"campaign_pending",
|
|
2253
|
-
"messaging_ready",
|
|
2254
|
-
"campaign_failed"
|
|
2255
|
-
]);
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
var OutboundAmdMode;
|
|
2362
|
+
var init_outbound = __esm({
|
|
2363
|
+
"../contracts/src/outbound.ts"() {
|
|
2364
|
+
OutboundAmdMode = z.enum(["off", "detect", "detectMessageEnd"]);
|
|
2256
2365
|
z.object({
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
/** Plain-language next step for the dashboard. */
|
|
2268
|
-
nextAction: z.string().nullable(),
|
|
2269
|
-
updatedAt: z.string().nullable()
|
|
2366
|
+
// Callee in E.164 (e.g. +14155551234). call-router re-validates strictly.
|
|
2367
|
+
to: z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164, e.g. +14155551234"),
|
|
2368
|
+
// Caller-id phone number (must belong to the calling project).
|
|
2369
|
+
phoneNumberId: z.string().min(1),
|
|
2370
|
+
// Optional agent override; falls back to the number's bound agent.
|
|
2371
|
+
agentId: z.string().min(1).optional(),
|
|
2372
|
+
// Template/context variables forwarded to the agent as dispatch metadata.
|
|
2373
|
+
variables: z.record(z.string(), z.string()).optional(),
|
|
2374
|
+
// Per-call AMD override; takes precedence over the agent's stored outbound.amd.
|
|
2375
|
+
amd: OutboundAmdMode.optional()
|
|
2270
2376
|
});
|
|
2271
2377
|
}
|
|
2272
2378
|
});
|
|
2273
|
-
var
|
|
2274
|
-
var
|
|
2275
|
-
"../contracts/src/
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2379
|
+
var ConnectionAuthType, ConnectionStatus;
|
|
2380
|
+
var init_connection = __esm({
|
|
2381
|
+
"../contracts/src/connection.ts"() {
|
|
2382
|
+
init_providers();
|
|
2383
|
+
ConnectionAuthType = z.enum(["oauth2", "api_key", "bearer", "basic", "none"]);
|
|
2384
|
+
ConnectionStatus = z.enum(["active", "revoked", "error"]);
|
|
2385
|
+
z.object({
|
|
2386
|
+
v: z.number().int(),
|
|
2387
|
+
// Key id (short fingerprint) of the key that encrypted this envelope — lets the
|
|
2388
|
+
// cipher pick the right key from its ring so CREDENTIAL_ENCRYPTION_KEY can rotate
|
|
2389
|
+
// in-band (Tier-1 item 4). Optional/absent on legacy v1 envelopes, which decrypt
|
|
2390
|
+
// with the designated legacy key.
|
|
2391
|
+
kid: z.string().optional(),
|
|
2392
|
+
iv: z.string(),
|
|
2393
|
+
ct: z.string(),
|
|
2394
|
+
tag: z.string()
|
|
2395
|
+
});
|
|
2279
2396
|
z.object({
|
|
2280
2397
|
id: z.string().uuid(),
|
|
2281
2398
|
projectId: z.string().min(1),
|
|
2282
2399
|
name: z.string().min(1).max(128),
|
|
2283
|
-
|
|
2284
|
-
//
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
//
|
|
2289
|
-
defaultModel: z.string().nullable(),
|
|
2290
|
-
normalize: ConnectorNormalize,
|
|
2291
|
-
enabled: z.boolean(),
|
|
2292
|
-
status: ConnectorStatus,
|
|
2293
|
-
lastSeenAt: z.string().datetime().nullable(),
|
|
2400
|
+
// Open string — customers name providers freely (e.g. 'google_calendar',
|
|
2401
|
+
// 'salesforce', 'n8n', 'http').
|
|
2402
|
+
provider: z.string().min(1).max(120),
|
|
2403
|
+
authType: ConnectionAuthType,
|
|
2404
|
+
status: ConnectionStatus,
|
|
2405
|
+
// Non-secret metadata (scopes, account label, base URL, …).
|
|
2294
2406
|
metadata: z.record(z.unknown()),
|
|
2407
|
+
expiresAt: z.string().datetime().nullable(),
|
|
2295
2408
|
createdAt: z.string().datetime(),
|
|
2296
2409
|
updatedAt: z.string().datetime()
|
|
2297
2410
|
});
|
|
2298
2411
|
z.object({
|
|
2299
2412
|
name: z.string().trim().min(1).max(128),
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2413
|
+
provider: z.string().trim().min(1).max(120),
|
|
2414
|
+
authType: ConnectionAuthType,
|
|
2415
|
+
// The secret material to encrypt at rest. Never stored or returned in plaintext.
|
|
2416
|
+
secret: z.string().min(1).max(8192),
|
|
2417
|
+
metadata: z.record(z.unknown()).optional(),
|
|
2418
|
+
expiresAt: z.string().datetime().optional()
|
|
2306
2419
|
});
|
|
2307
2420
|
z.object({
|
|
2308
2421
|
name: z.string().trim().min(1).max(128).optional(),
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
})
|
|
2314
|
-
|
|
2315
|
-
});
|
|
2316
|
-
var CONNECTOR_REQUEST_CHANNEL, CONNECTOR_REPLY_CHANNEL, BrainWireMessage, HelloFrame, PongFrame, RegisteredFrame, PingFrame, ShutdownFrame, BrainRequestFrame, BrainCancelFrame, BrainDeltaFrame, BrainDoneFrame, BrainErrorFrame, ConnectorUpFrame;
|
|
2317
|
-
var init_connector_stream = __esm({
|
|
2318
|
-
"../contracts/src/connector-stream.ts"() {
|
|
2319
|
-
CONNECTOR_REQUEST_CHANNEL = (connectorId) => `brain-req:${connectorId}`;
|
|
2320
|
-
CONNECTOR_REPLY_CHANNEL = (streamId) => `brain-reply:${streamId}`;
|
|
2321
|
-
BrainWireMessage = z.object({
|
|
2322
|
-
role: z.enum(["system", "user", "assistant"]),
|
|
2323
|
-
content: z.string()
|
|
2324
|
-
});
|
|
2325
|
-
HelloFrame = z.object({
|
|
2326
|
-
kind: z.literal("hello"),
|
|
2327
|
-
token: z.string().min(1),
|
|
2328
|
-
replicaId: z.string().min(1),
|
|
2329
|
-
version: z.string().min(1),
|
|
2330
|
-
caps: z.object({ streaming: z.boolean(), tools: z.boolean(), litellm: z.boolean() })
|
|
2331
|
-
});
|
|
2332
|
-
PongFrame = z.object({ kind: z.literal("pong"), ts: z.number().int() });
|
|
2333
|
-
RegisteredFrame = z.object({
|
|
2334
|
-
kind: z.literal("registered"),
|
|
2335
|
-
connectorId: z.string().uuid(),
|
|
2336
|
-
projectId: z.string().min(1)
|
|
2337
|
-
});
|
|
2338
|
-
PingFrame = z.object({ kind: z.literal("ping"), ts: z.number().int() });
|
|
2339
|
-
ShutdownFrame = z.object({ kind: z.literal("shutdown"), reason: z.string().optional() });
|
|
2340
|
-
BrainRequestFrame = z.object({
|
|
2341
|
-
kind: z.literal("brain.request"),
|
|
2342
|
-
streamId: z.string().min(1),
|
|
2343
|
-
model: z.string().optional(),
|
|
2344
|
-
temperature: z.number().optional(),
|
|
2345
|
-
messages: z.array(BrainWireMessage)
|
|
2422
|
+
// New secret material — re-encrypted at rest; the old envelope is replaced.
|
|
2423
|
+
secret: z.string().min(1).max(8192).optional(),
|
|
2424
|
+
// Merged over the existing metadata (top-level keys win).
|
|
2425
|
+
metadata: z.record(z.unknown()).optional()
|
|
2426
|
+
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
2427
|
+
message: "nothing to update"
|
|
2346
2428
|
});
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2429
|
+
byokProviders().map((p) => ({
|
|
2430
|
+
id: p.id,
|
|
2431
|
+
label: p.label,
|
|
2432
|
+
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
2433
|
+
supportsBaseUrl: p.supportsBaseUrl
|
|
2434
|
+
}));
|
|
2435
|
+
z.object({
|
|
2436
|
+
id: z.string().min(1),
|
|
2437
|
+
label: z.string().min(1),
|
|
2438
|
+
/** The vault `provider` the resulting connection is stored under. */
|
|
2439
|
+
vaultProvider: z.string().min(1),
|
|
2440
|
+
category: z.enum(["model", "tool"])
|
|
2350
2441
|
});
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
streamId: z.string().min(1),
|
|
2354
|
-
content: z.string()
|
|
2442
|
+
z.object({
|
|
2443
|
+
authorizeUrl: z.string().url()
|
|
2355
2444
|
});
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
finishReason: z.string()
|
|
2445
|
+
z.object({
|
|
2446
|
+
state: z.string().min(1).max(512),
|
|
2447
|
+
code: z.string().min(1).max(4096)
|
|
2360
2448
|
});
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2449
|
+
z.object({
|
|
2450
|
+
providerId: z.string().min(1),
|
|
2451
|
+
/** The provider's display name ("ChatGPT"). */
|
|
2452
|
+
providerLabel: z.string().min(1),
|
|
2453
|
+
/** What the connection is for — which group the dashboard files it under. */
|
|
2454
|
+
category: z.enum(["model", "tool"]),
|
|
2455
|
+
/** The account's stable id at the provider (OIDC `sub`); reconnecting the same account updates the same row. */
|
|
2456
|
+
subject: z.string().min(1).nullable(),
|
|
2457
|
+
email: z.string().nullable(),
|
|
2458
|
+
displayName: z.string().nullable(),
|
|
2459
|
+
scopes: z.array(z.string()),
|
|
2460
|
+
/**
|
|
2461
|
+
* The workspace user who signed in. A person's model plan (category 'model') serves only them — never teammates or
|
|
2462
|
+
* API keys. Absent on rows connected before it was recorded.
|
|
2463
|
+
*/
|
|
2464
|
+
connectedBy: z.string().nullable().optional(),
|
|
2465
|
+
/** Set when a refresh was refused (e.g. `invalid_grant`): the owner must connect again. */
|
|
2466
|
+
reauthRequired: z.boolean().optional()
|
|
2372
2467
|
});
|
|
2373
|
-
ConnectorUpFrame = z.discriminatedUnion("kind", [
|
|
2374
|
-
HelloFrame,
|
|
2375
|
-
PongFrame,
|
|
2376
|
-
BrainDeltaFrame,
|
|
2377
|
-
BrainDoneFrame,
|
|
2378
|
-
BrainErrorFrame
|
|
2379
|
-
]);
|
|
2380
|
-
z.discriminatedUnion("kind", [
|
|
2381
|
-
RegisteredFrame,
|
|
2382
|
-
PingFrame,
|
|
2383
|
-
ShutdownFrame,
|
|
2384
|
-
BrainRequestFrame,
|
|
2385
|
-
BrainCancelFrame
|
|
2386
|
-
]);
|
|
2387
|
-
}
|
|
2388
|
-
});
|
|
2389
|
-
|
|
2390
|
-
// ../contracts/src/flow-enrichment.ts
|
|
2391
|
-
function planEnrichment(graph) {
|
|
2392
|
-
const ids = graph.nodes.map((n) => n.id);
|
|
2393
|
-
const indeg = new Map(ids.map((id) => [id, 0]));
|
|
2394
|
-
const adj = new Map(ids.map((id) => [id, []]));
|
|
2395
|
-
for (const edge of graph.edges) {
|
|
2396
|
-
if (!adj.has(edge.source) || !indeg.has(edge.target)) continue;
|
|
2397
|
-
adj.get(edge.source).push(edge.target);
|
|
2398
|
-
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2399
|
-
}
|
|
2400
|
-
const queue = ids.filter((id) => (indeg.get(id) ?? 0) === 0);
|
|
2401
|
-
const order = [];
|
|
2402
|
-
while (queue.length > 0) {
|
|
2403
|
-
const id = queue.shift();
|
|
2404
|
-
order.push(id);
|
|
2405
|
-
for (const next of adj.get(id) ?? []) {
|
|
2406
|
-
const left = (indeg.get(next) ?? 0) - 1;
|
|
2407
|
-
indeg.set(next, left);
|
|
2408
|
-
if (left === 0) queue.push(next);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
return { order, acyclic: order.length === ids.length };
|
|
2412
|
-
}
|
|
2413
|
-
var init_flow_enrichment = __esm({
|
|
2414
|
-
"../contracts/src/flow-enrichment.ts"() {
|
|
2415
2468
|
}
|
|
2416
2469
|
});
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
return {
|
|
2487
|
-
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
2488
|
-
required: data.required !== false,
|
|
2489
|
-
type: asFieldType(data.type),
|
|
2490
|
-
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2491
|
-
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
2492
|
-
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
2493
|
-
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2494
|
-
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2495
|
-
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2496
|
-
};
|
|
2497
|
-
case "llm":
|
|
2498
|
-
return {
|
|
2499
|
-
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
2500
|
-
// Chat-hub valve: a resident conversational node (entry/return point)
|
|
2501
|
-
// that routes to task subgraphs on its conditional edges and absorbs
|
|
2502
|
-
// off-script turns in place instead of dead-ending. See the interpreter.
|
|
2503
|
-
hub: data.hub === true,
|
|
2504
|
-
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2505
|
-
// Drift-gate (supervisor-lite): verify the reply still localizes to this
|
|
2506
|
-
// node. enforce → reject-and-regenerate, then speak `fallback` verbatim.
|
|
2507
|
-
adherence: asAdherence(data.adherence),
|
|
2508
|
-
...typeof data.fallback === "string" ? { fallback: data.fallback } : {}
|
|
2509
|
-
};
|
|
2510
|
-
case "tool": {
|
|
2511
|
-
const url = typeof data.url === "string" ? data.url : "";
|
|
2512
|
-
const rawName = typeof data.name === "string" ? data.name : "";
|
|
2513
|
-
const name = /^[a-z][a-z0-9_]*$/.test(rawName) ? rawName : `tool_${node.id.replace(/[^a-z0-9]/gi, "").slice(0, 16).toLowerCase() || "fn"}`;
|
|
2514
|
-
const input = {};
|
|
2515
|
-
if (data.input && typeof data.input === "object") {
|
|
2516
|
-
for (const [key, val] of Object.entries(data.input)) {
|
|
2517
|
-
if (val === "string" || val === "number" || val === "boolean") input[key] = val;
|
|
2518
|
-
}
|
|
2470
|
+
var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
|
|
2471
|
+
var init_telephony = __esm({
|
|
2472
|
+
"../contracts/src/telephony.ts"() {
|
|
2473
|
+
E164 = z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164 (+15551234567)");
|
|
2474
|
+
RoomPrefix = z.string().regex(/^[a-z0-9][a-z0-9-]{0,30}[a-z0-9-]?$/, "lowercase letters, digits, dashes; max 32");
|
|
2475
|
+
RecordingPrefix = z.string().min(1).max(128).refine((v) => !v.startsWith("/") && !v.includes(".."), "relative key prefix only");
|
|
2476
|
+
Cidr = z.string().regex(/^\d{1,3}(\.\d{1,3}){3}\/\d{1,2}$/, "must be IPv4 CIDR (a.b.c.d/nn)");
|
|
2477
|
+
TelephonySettings = z.object({
|
|
2478
|
+
outboundTrunkId: z.string().min(1).max(128).nullable().default(null),
|
|
2479
|
+
defaultFromE164: E164.nullable().default(null),
|
|
2480
|
+
roomPrefix: RoomPrefix.nullable().default(null),
|
|
2481
|
+
sipAllowedCidrs: z.array(Cidr).max(32).nullable().default(null),
|
|
2482
|
+
recordingPrefix: RecordingPrefix.nullable().default(null),
|
|
2483
|
+
strictByok: z.boolean().default(false)
|
|
2484
|
+
});
|
|
2485
|
+
z.object({
|
|
2486
|
+
outboundTrunkId: z.string().min(1).max(128).nullable().optional(),
|
|
2487
|
+
defaultFromE164: E164.nullable().optional(),
|
|
2488
|
+
roomPrefix: RoomPrefix.nullable().optional(),
|
|
2489
|
+
sipAllowedCidrs: z.array(Cidr).max(32).nullable().optional(),
|
|
2490
|
+
recordingPrefix: RecordingPrefix.nullable().optional(),
|
|
2491
|
+
strictByok: z.boolean().optional()
|
|
2492
|
+
}).refine((v) => Object.keys(v).length > 0, { message: "nothing to update" });
|
|
2493
|
+
ProfileSource = z.enum(["org", "platform", "none"]);
|
|
2494
|
+
EffectiveCallProfile = z.object({
|
|
2495
|
+
projectId: z.string(),
|
|
2496
|
+
outboundTrunkId: z.string().nullable(),
|
|
2497
|
+
defaultFromE164: z.string().nullable(),
|
|
2498
|
+
roomPrefix: z.string(),
|
|
2499
|
+
sipAllowedCidrs: z.array(z.string()),
|
|
2500
|
+
recordingPrefix: z.string(),
|
|
2501
|
+
strictByok: z.boolean(),
|
|
2502
|
+
sources: z.object({
|
|
2503
|
+
outboundTrunkId: ProfileSource,
|
|
2504
|
+
defaultFromE164: ProfileSource,
|
|
2505
|
+
roomPrefix: ProfileSource,
|
|
2506
|
+
sipAllowedCidrs: ProfileSource,
|
|
2507
|
+
recordingPrefix: ProfileSource
|
|
2508
|
+
})
|
|
2509
|
+
});
|
|
2510
|
+
z.object({
|
|
2511
|
+
settings: TelephonySettings,
|
|
2512
|
+
profile: EffectiveCallProfile
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
2516
|
+
var SHORTENER_HOSTS, PLACEHOLDER_HOSTS, PublicUrl, A2pBusinessType, A2pBusinessInfo, A2pOptInType, A2pUseCase, A2pMessagingProfile, A2pState;
|
|
2517
|
+
var init_a2p = __esm({
|
|
2518
|
+
"../contracts/src/a2p.ts"() {
|
|
2519
|
+
SHORTENER_HOSTS = [
|
|
2520
|
+
"bit.ly",
|
|
2521
|
+
"tinyurl.com",
|
|
2522
|
+
"t.co",
|
|
2523
|
+
"goo.gl",
|
|
2524
|
+
"ow.ly",
|
|
2525
|
+
"is.gd",
|
|
2526
|
+
"buff.ly",
|
|
2527
|
+
"rebrand.ly",
|
|
2528
|
+
"cutt.ly",
|
|
2529
|
+
"shorturl.at",
|
|
2530
|
+
"tiny.cc"
|
|
2531
|
+
];
|
|
2532
|
+
PLACEHOLDER_HOSTS = ["acme.com", "example.com", "example.org", "test.com", "localhost"];
|
|
2533
|
+
PublicUrl = z.string().url().refine((v) => v.startsWith("https://"), "must be https").refine((v) => {
|
|
2534
|
+
try {
|
|
2535
|
+
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2536
|
+
return !SHORTENER_HOSTS.includes(host);
|
|
2537
|
+
} catch {
|
|
2538
|
+
return false;
|
|
2519
2539
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2540
|
+
}, "public URL shorteners are rejected by carriers \u2014 use your own domain").refine((v) => {
|
|
2541
|
+
try {
|
|
2542
|
+
const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
|
|
2543
|
+
return !PLACEHOLDER_HOSTS.some((p) => host === p || host.endsWith(`.${p}`));
|
|
2544
|
+
} catch {
|
|
2545
|
+
return false;
|
|
2525
2546
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2547
|
+
}, "placeholder domain \u2014 reviewers will follow this link and reject the campaign");
|
|
2548
|
+
A2pBusinessType = z.enum([
|
|
2549
|
+
"Sole Proprietorship",
|
|
2550
|
+
"Partnership",
|
|
2551
|
+
"Corporation",
|
|
2552
|
+
"Co-operative",
|
|
2553
|
+
"Limited Liability Corporation",
|
|
2554
|
+
"Non-profit Corporation"
|
|
2555
|
+
]);
|
|
2556
|
+
A2pBusinessInfo = z.object({
|
|
2557
|
+
legalName: z.string().trim().min(2).max(200),
|
|
2558
|
+
/** EIN (US) or equivalent registration number. */
|
|
2559
|
+
registrationNumber: z.string().trim().min(4).max(50),
|
|
2560
|
+
businessType: A2pBusinessType,
|
|
2561
|
+
/** Publicly reachable production site — not staging, not a 404. */
|
|
2562
|
+
website: PublicUrl,
|
|
2563
|
+
industry: z.string().trim().min(2).max(60),
|
|
2564
|
+
address: z.object({
|
|
2565
|
+
street: z.string().trim().min(2).max(200),
|
|
2566
|
+
city: z.string().trim().min(1).max(100),
|
|
2567
|
+
region: z.string().trim().min(1).max(100),
|
|
2568
|
+
postalCode: z.string().trim().min(2).max(20),
|
|
2569
|
+
isoCountry: z.string().trim().length(2)
|
|
2570
|
+
}),
|
|
2571
|
+
authorizedRep: z.object({
|
|
2572
|
+
firstName: z.string().trim().min(1).max(100),
|
|
2573
|
+
lastName: z.string().trim().min(1).max(100),
|
|
2574
|
+
email: z.string().trim().email(),
|
|
2575
|
+
phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
|
|
2576
|
+
jobTitle: z.string().trim().min(2).max(100)
|
|
2577
|
+
})
|
|
2578
|
+
});
|
|
2579
|
+
A2pOptInType = z.enum(["WEB_FORM", "PAPER_FORM", "VERBAL", "VIA_TEXT", "MOBILE_QR_CODE"]);
|
|
2580
|
+
A2pUseCase = z.enum([
|
|
2581
|
+
"MIXED",
|
|
2582
|
+
"CUSTOMER_CARE",
|
|
2583
|
+
"MARKETING",
|
|
2584
|
+
"ACCOUNT_NOTIFICATION",
|
|
2585
|
+
"2FA",
|
|
2586
|
+
"DELIVERY_NOTIFICATION",
|
|
2587
|
+
"HIGHER_EDUCATION",
|
|
2588
|
+
"POLLING_VOTING",
|
|
2589
|
+
"PUBLIC_SERVICE_ANNOUNCEMENT",
|
|
2590
|
+
"LOW_VOLUME"
|
|
2591
|
+
]);
|
|
2592
|
+
A2pMessagingProfile = z.object({
|
|
2593
|
+
useCase: A2pUseCase,
|
|
2594
|
+
/** Specific, not generic. "We send texts" gets rejected; describe the actual
|
|
2595
|
+
* messages and when they are sent. */
|
|
2596
|
+
description: z.string().trim().min(40).max(4096),
|
|
2597
|
+
/**
|
|
2598
|
+
* The single most-rejected field. Must describe HOW people opt in, state the
|
|
2599
|
+
* message frequency, include the "message and data rates may apply"
|
|
2600
|
+
* disclosure, and link to publicly reachable evidence. Twilio's API bounds it
|
|
2601
|
+
* to 40–2049 characters.
|
|
2602
|
+
*/
|
|
2603
|
+
messageFlow: z.string().trim().min(40).max(2049),
|
|
2604
|
+
optInType: A2pOptInType,
|
|
2605
|
+
/** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
|
|
2606
|
+
* these; anything behind a login fails. */
|
|
2607
|
+
optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
|
|
2608
|
+
/** Real messages the customer will send. Must reflect the declared use case
|
|
2609
|
+
* and carry opt-out language. */
|
|
2610
|
+
messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
|
|
2611
|
+
privacyPolicyUrl: PublicUrl,
|
|
2612
|
+
termsAndConditionsUrl: PublicUrl,
|
|
2613
|
+
hasEmbeddedLinks: z.boolean().default(false),
|
|
2614
|
+
hasEmbeddedPhone: z.boolean().default(false)
|
|
2615
|
+
});
|
|
2616
|
+
z.object({
|
|
2617
|
+
business: A2pBusinessInfo,
|
|
2618
|
+
messaging: A2pMessagingProfile,
|
|
2619
|
+
/** Register against Twilio's mock endpoints — exercises the full pipeline
|
|
2620
|
+
* with no fees and no real carrier submission. Used in CI and staging. */
|
|
2621
|
+
mock: z.boolean().default(false)
|
|
2622
|
+
}).superRefine((v, ctx) => {
|
|
2623
|
+
const flow = v.messaging.messageFlow.toLowerCase();
|
|
2624
|
+
if (!/(msg|message)\s*(&|and)\s*data rates/.test(flow)) {
|
|
2625
|
+
ctx.addIssue({
|
|
2626
|
+
code: z.ZodIssueCode.custom,
|
|
2627
|
+
path: ["messaging", "messageFlow"],
|
|
2628
|
+
message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
|
|
2629
|
+
});
|
|
2630
|
+
}
|
|
2631
|
+
if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
|
|
2632
|
+
ctx.addIssue({
|
|
2633
|
+
code: z.ZodIssueCode.custom,
|
|
2634
|
+
path: ["messaging", "messageFlow"],
|
|
2635
|
+
message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
|
|
2639
|
+
if (!hasOptOut) {
|
|
2640
|
+
ctx.addIssue({
|
|
2641
|
+
code: z.ZodIssueCode.custom,
|
|
2642
|
+
path: ["messaging", "messageSamples"],
|
|
2643
|
+
message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
});
|
|
2647
|
+
A2pState = z.enum([
|
|
2648
|
+
"none",
|
|
2649
|
+
"profile_pending",
|
|
2650
|
+
"profile_approved",
|
|
2651
|
+
"profile_failed",
|
|
2652
|
+
"brand_pending",
|
|
2653
|
+
"brand_approved",
|
|
2654
|
+
"brand_failed",
|
|
2655
|
+
"campaign_pending",
|
|
2656
|
+
"messaging_ready",
|
|
2657
|
+
"campaign_failed"
|
|
2658
|
+
]);
|
|
2659
|
+
z.object({
|
|
2660
|
+
state: A2pState,
|
|
2661
|
+
customerProfileSid: z.string().nullable(),
|
|
2662
|
+
trustProductSid: z.string().nullable(),
|
|
2663
|
+
brandSid: z.string().nullable(),
|
|
2664
|
+
messagingServiceSid: z.string().nullable(),
|
|
2665
|
+
campaignSid: z.string().nullable(),
|
|
2666
|
+
mock: z.boolean(),
|
|
2667
|
+
/** Carrier/Twilio rejection details, surfaced verbatim so the customer can
|
|
2668
|
+
* fix the specific field rather than guess. */
|
|
2669
|
+
failures: z.array(z.object({ code: z.number().nullable(), field: z.string().nullable(), message: z.string() })).default([]),
|
|
2670
|
+
/** Plain-language next step for the dashboard. */
|
|
2671
|
+
nextAction: z.string().nullable(),
|
|
2672
|
+
updatedAt: z.string().nullable()
|
|
2673
|
+
});
|
|
2654
2674
|
}
|
|
2655
|
-
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2675
|
+
});
|
|
2676
|
+
var ConnectorMode, ConnectorStatus, ConnectorNormalize;
|
|
2677
|
+
var init_connector = __esm({
|
|
2678
|
+
"../contracts/src/connector.ts"() {
|
|
2679
|
+
ConnectorMode = z.enum(["tunnel", "direct"]);
|
|
2680
|
+
ConnectorStatus = z.enum(["online", "degraded", "offline"]);
|
|
2681
|
+
ConnectorNormalize = z.enum(["auto", "on", "off"]);
|
|
2682
|
+
z.object({
|
|
2683
|
+
id: z.string().uuid(),
|
|
2684
|
+
projectId: z.string().min(1),
|
|
2685
|
+
name: z.string().min(1).max(128),
|
|
2686
|
+
mode: ConnectorMode,
|
|
2687
|
+
// Tunnel mode: a short non-secret preview of the daemon token. Null for direct.
|
|
2688
|
+
tokenPreview: z.string().nullable(),
|
|
2689
|
+
// Direct mode: the connections-vault row holding the URL + key. Null for tunnel.
|
|
2690
|
+
connectionId: z.string().uuid().nullable(),
|
|
2691
|
+
// Upstream model id / alias. Null → the brain's own default.
|
|
2692
|
+
defaultModel: z.string().nullable(),
|
|
2693
|
+
normalize: ConnectorNormalize,
|
|
2694
|
+
enabled: z.boolean(),
|
|
2695
|
+
status: ConnectorStatus,
|
|
2696
|
+
lastSeenAt: z.string().datetime().nullable(),
|
|
2697
|
+
metadata: z.record(z.unknown()),
|
|
2698
|
+
createdAt: z.string().datetime(),
|
|
2699
|
+
updatedAt: z.string().datetime()
|
|
2700
|
+
});
|
|
2701
|
+
z.object({
|
|
2702
|
+
name: z.string().trim().min(1).max(128),
|
|
2703
|
+
mode: ConnectorMode,
|
|
2704
|
+
// Required when mode === 'direct': the vault connection holding URL + key.
|
|
2705
|
+
connectionId: z.string().uuid().optional(),
|
|
2706
|
+
defaultModel: z.string().trim().min(1).max(200).optional(),
|
|
2707
|
+
normalize: ConnectorNormalize.optional(),
|
|
2708
|
+
metadata: z.record(z.unknown()).optional()
|
|
2709
|
+
});
|
|
2710
|
+
z.object({
|
|
2711
|
+
name: z.string().trim().min(1).max(128).optional(),
|
|
2712
|
+
connectionId: z.string().uuid().nullable().optional(),
|
|
2713
|
+
defaultModel: z.string().trim().min(1).max(200).nullable().optional(),
|
|
2714
|
+
normalize: ConnectorNormalize.optional(),
|
|
2715
|
+
enabled: z.boolean().optional()
|
|
2678
2716
|
});
|
|
2679
|
-
outBySource.set(edge.source, list);
|
|
2680
|
-
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2681
2717
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2718
|
+
});
|
|
2719
|
+
var CONNECTOR_REQUEST_CHANNEL, CONNECTOR_REPLY_CHANNEL, BrainWireMessage, HelloFrame, PongFrame, RegisteredFrame, PingFrame, ShutdownFrame, BrainRequestFrame, BrainCancelFrame, BrainDeltaFrame, BrainDoneFrame, BrainErrorFrame, ConnectorUpFrame;
|
|
2720
|
+
var init_connector_stream = __esm({
|
|
2721
|
+
"../contracts/src/connector-stream.ts"() {
|
|
2722
|
+
CONNECTOR_REQUEST_CHANNEL = (connectorId) => `brain-req:${connectorId}`;
|
|
2723
|
+
CONNECTOR_REPLY_CHANNEL = (streamId) => `brain-reply:${streamId}`;
|
|
2724
|
+
BrainWireMessage = z.object({
|
|
2725
|
+
role: z.enum(["system", "user", "assistant"]),
|
|
2726
|
+
content: z.string()
|
|
2727
|
+
});
|
|
2728
|
+
HelloFrame = z.object({
|
|
2729
|
+
kind: z.literal("hello"),
|
|
2730
|
+
token: z.string().min(1),
|
|
2731
|
+
replicaId: z.string().min(1),
|
|
2732
|
+
version: z.string().min(1),
|
|
2733
|
+
caps: z.object({ streaming: z.boolean(), tools: z.boolean(), litellm: z.boolean() })
|
|
2734
|
+
});
|
|
2735
|
+
PongFrame = z.object({ kind: z.literal("pong"), ts: z.number().int() });
|
|
2736
|
+
RegisteredFrame = z.object({
|
|
2737
|
+
kind: z.literal("registered"),
|
|
2738
|
+
connectorId: z.string().uuid(),
|
|
2739
|
+
projectId: z.string().min(1)
|
|
2740
|
+
});
|
|
2741
|
+
PingFrame = z.object({ kind: z.literal("ping"), ts: z.number().int() });
|
|
2742
|
+
ShutdownFrame = z.object({ kind: z.literal("shutdown"), reason: z.string().optional() });
|
|
2743
|
+
BrainRequestFrame = z.object({
|
|
2744
|
+
kind: z.literal("brain.request"),
|
|
2745
|
+
streamId: z.string().min(1),
|
|
2746
|
+
model: z.string().optional(),
|
|
2747
|
+
temperature: z.number().optional(),
|
|
2748
|
+
messages: z.array(BrainWireMessage)
|
|
2749
|
+
});
|
|
2750
|
+
BrainCancelFrame = z.object({
|
|
2751
|
+
kind: z.literal("brain.cancel"),
|
|
2752
|
+
streamId: z.string().min(1)
|
|
2753
|
+
});
|
|
2754
|
+
BrainDeltaFrame = z.object({
|
|
2755
|
+
kind: z.literal("brain.delta"),
|
|
2756
|
+
streamId: z.string().min(1),
|
|
2757
|
+
content: z.string()
|
|
2758
|
+
});
|
|
2759
|
+
BrainDoneFrame = z.object({
|
|
2760
|
+
kind: z.literal("brain.done"),
|
|
2761
|
+
streamId: z.string().min(1),
|
|
2762
|
+
finishReason: z.string()
|
|
2763
|
+
});
|
|
2764
|
+
BrainErrorFrame = z.object({
|
|
2765
|
+
kind: z.literal("brain.error"),
|
|
2766
|
+
streamId: z.string().min(1),
|
|
2767
|
+
code: z.enum([
|
|
2768
|
+
"upstream_timeout",
|
|
2769
|
+
"upstream_error",
|
|
2770
|
+
"bad_response",
|
|
2771
|
+
"normalize_failed",
|
|
2772
|
+
"unreachable"
|
|
2773
|
+
]),
|
|
2774
|
+
message: z.string()
|
|
2775
|
+
});
|
|
2776
|
+
ConnectorUpFrame = z.discriminatedUnion("kind", [
|
|
2777
|
+
HelloFrame,
|
|
2778
|
+
PongFrame,
|
|
2779
|
+
BrainDeltaFrame,
|
|
2780
|
+
BrainDoneFrame,
|
|
2781
|
+
BrainErrorFrame
|
|
2782
|
+
]);
|
|
2783
|
+
z.discriminatedUnion("kind", [
|
|
2784
|
+
RegisteredFrame,
|
|
2785
|
+
PingFrame,
|
|
2786
|
+
ShutdownFrame,
|
|
2787
|
+
BrainRequestFrame,
|
|
2788
|
+
BrainCancelFrame
|
|
2789
|
+
]);
|
|
2695
2790
|
}
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
entry: entry2.slice(0, 64),
|
|
2705
|
-
nodes,
|
|
2706
|
-
completionGate: { requiredFields: completionGate.requiredFields.slice(0, 64) },
|
|
2707
|
-
// Diagnostics arrays are schema-capped at 256 — slice so a pathological
|
|
2708
|
-
// graph can't fail the read-side parse.
|
|
2709
|
-
diagnostics: {
|
|
2710
|
-
acyclic,
|
|
2711
|
-
droppedEdges: droppedEdges.slice(0, 256),
|
|
2712
|
-
unsupportedNodes: unsupportedNodes.slice(0, 256),
|
|
2713
|
-
labelOnlyEdges: labelOnlyEdges.slice(0, 256)
|
|
2714
|
-
}
|
|
2715
|
-
};
|
|
2716
|
-
}
|
|
2717
|
-
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2718
|
-
var init_flow_compile = __esm({
|
|
2719
|
-
"../contracts/src/flow-compile.ts"() {
|
|
2720
|
-
init_flow_enrichment();
|
|
2721
|
-
init_flow_validate();
|
|
2722
|
-
FIELD_TYPES = [
|
|
2723
|
-
"string",
|
|
2724
|
-
"string[]",
|
|
2725
|
-
"number",
|
|
2726
|
-
"boolean",
|
|
2727
|
-
"datetime",
|
|
2728
|
-
"email",
|
|
2729
|
-
"phone"
|
|
2730
|
-
];
|
|
2731
|
-
HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
2732
|
-
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2733
|
-
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2734
|
-
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2735
|
-
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2736
|
-
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
2791
|
+
});
|
|
2792
|
+
var FLOW_BOOT_FAILURES;
|
|
2793
|
+
var init_call_end_reason = __esm({
|
|
2794
|
+
"../contracts/src/call-end-reason.ts"() {
|
|
2795
|
+
FLOW_BOOT_FAILURES = ["schema_fetch_failed", "no_process_schema", "missing_agent_id", "no_api_key"];
|
|
2796
|
+
z.enum(
|
|
2797
|
+
FLOW_BOOT_FAILURES.map((cause) => `flow_boot:${cause}`)
|
|
2798
|
+
);
|
|
2737
2799
|
}
|
|
2738
2800
|
});
|
|
2739
2801
|
var EnvironmentSpec;
|
|
@@ -3972,6 +4034,8 @@ var init_src = __esm({
|
|
|
3972
4034
|
init_connector();
|
|
3973
4035
|
init_connector_stream();
|
|
3974
4036
|
init_flow_compile();
|
|
4037
|
+
init_flow_compile_check();
|
|
4038
|
+
init_call_end_reason();
|
|
3975
4039
|
init_flow_enrichment();
|
|
3976
4040
|
init_environments();
|
|
3977
4041
|
init_variables();
|
|
@@ -9823,7 +9887,8 @@ var CallsClient = class {
|
|
|
9823
9887
|
body: {
|
|
9824
9888
|
...input.attributes !== void 0 ? { attributes: input.attributes } : {},
|
|
9825
9889
|
...input.participants !== void 0 ? { participants: input.participants } : {},
|
|
9826
|
-
...input.legs !== void 0 ? { legs: input.legs } : {}
|
|
9890
|
+
...input.legs !== void 0 ? { legs: input.legs } : {},
|
|
9891
|
+
...input.endReason !== void 0 ? { endReason: input.endReason } : {}
|
|
9827
9892
|
}
|
|
9828
9893
|
},
|
|
9829
9894
|
CallStateSyncResponse
|
|
@@ -14433,6 +14498,7 @@ function guardIsActive(opts) {
|
|
|
14433
14498
|
}
|
|
14434
14499
|
|
|
14435
14500
|
// src/runtime/call-sync.ts
|
|
14501
|
+
var END_REASON_WAIT_MS = 2e3;
|
|
14436
14502
|
function makeNoopHandle(callId) {
|
|
14437
14503
|
return {
|
|
14438
14504
|
callId,
|
|
@@ -14441,6 +14507,8 @@ function makeNoopHandle(callId) {
|
|
|
14441
14507
|
syncState() {
|
|
14442
14508
|
},
|
|
14443
14509
|
publishDtmf() {
|
|
14510
|
+
},
|
|
14511
|
+
async recordEndReason() {
|
|
14444
14512
|
}
|
|
14445
14513
|
};
|
|
14446
14514
|
}
|
|
@@ -14481,6 +14549,24 @@ async function createCallSyncHandle(init) {
|
|
|
14481
14549
|
});
|
|
14482
14550
|
});
|
|
14483
14551
|
},
|
|
14552
|
+
async recordEndReason(reason) {
|
|
14553
|
+
let timer;
|
|
14554
|
+
try {
|
|
14555
|
+
await Promise.race([
|
|
14556
|
+
init.client.calls.syncState(callId, { endReason: reason }),
|
|
14557
|
+
new Promise((resolve) => {
|
|
14558
|
+
timer = setTimeout(resolve, END_REASON_WAIT_MS);
|
|
14559
|
+
})
|
|
14560
|
+
]);
|
|
14561
|
+
} catch (err) {
|
|
14562
|
+
console.warn("[sdk.call-sync] end reason not recorded", {
|
|
14563
|
+
reason,
|
|
14564
|
+
err: err instanceof Error ? err.message : err
|
|
14565
|
+
});
|
|
14566
|
+
} finally {
|
|
14567
|
+
clearTimeout(timer);
|
|
14568
|
+
}
|
|
14569
|
+
},
|
|
14484
14570
|
publishDtmf(input) {
|
|
14485
14571
|
void init.client.calls.appendDtmfEvent(callId, input).catch((err) => {
|
|
14486
14572
|
console.warn("[sdk.call-sync] dtmf publish failed", {
|
|
@@ -15942,6 +16028,7 @@ var Agent = class {
|
|
|
15942
16028
|
break;
|
|
15943
16029
|
} catch (err) {
|
|
15944
16030
|
fetchError = err;
|
|
16031
|
+
if (err instanceof VoiceLayerValidationError) break;
|
|
15945
16032
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
15946
16033
|
}
|
|
15947
16034
|
}
|
|
@@ -16756,6 +16843,7 @@ ${callIntent}`
|
|
|
16756
16843
|
}
|
|
16757
16844
|
}
|
|
16758
16845
|
} else if (flowBootFailure) {
|
|
16846
|
+
const reported = callSync.recordEndReason(`flow_boot:${flowBootFailure}`);
|
|
16759
16847
|
const apology = this.config.flowRuntime?.bootFailureMessage ?? "Sorry \u2014 we're having a technical issue starting this call. Please try again in a few minutes. Goodbye.";
|
|
16760
16848
|
try {
|
|
16761
16849
|
await adapter.generateReply({
|
|
@@ -16766,6 +16854,7 @@ ${apology}`
|
|
|
16766
16854
|
} catch (err) {
|
|
16767
16855
|
console.warn("[agent] flow boot apology failed to play", err);
|
|
16768
16856
|
}
|
|
16857
|
+
await reported;
|
|
16769
16858
|
await endCallController.hangUp({ trigger: "flow", reason: `flow_boot:${flowBootFailure}` });
|
|
16770
16859
|
} else if (this.config.onCall) {
|
|
16771
16860
|
await this.config.onCall(ctx);
|