@voicelayer/sdk 0.6.2 → 0.7.0
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.d.ts +2 -2
- package/dist/brain/index.js +226 -9
- package/dist/index.d.ts +259 -254
- package/dist/index.js +1643 -491
- package/dist/runtime/text-session.d.ts +1 -1
- package/dist/runtime/text-session.js +1214 -183
- package/dist/{text-session-B-T33y5P.d.ts → text-session-BI5A-gxI.d.ts} +132 -110
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -14,11 +14,11 @@ import { LoggerProvider, BatchLogRecordProcessor } from '@opentelemetry/sdk-logs
|
|
|
14
14
|
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
15
15
|
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
16
16
|
import { ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
|
|
17
|
+
import { llm, DEFAULT_API_CONNECT_OPTIONS, intervalForRetry, voice as voice$1, APIStatusError, APITimeoutError, APIConnectionError } from '@livekit/agents';
|
|
17
18
|
import http from 'http';
|
|
18
19
|
import https from 'https';
|
|
19
20
|
import { Readable } from 'stream';
|
|
20
21
|
import { lookup } from 'dns/promises';
|
|
21
|
-
import { voice as voice$1, llm } from '@livekit/agents';
|
|
22
22
|
import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten';
|
|
23
23
|
import { randomUUID, createHash } from 'crypto';
|
|
24
24
|
import { setup, createActor } from 'xstate';
|
|
@@ -95,35 +95,6 @@ var init_log = __esm({
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
// src/runtime/graph/graph-model.ts
|
|
99
|
-
function buildGraphModel(program) {
|
|
100
|
-
return {
|
|
101
|
-
entry: program.entry,
|
|
102
|
-
node: (id) => program.nodes[id],
|
|
103
|
-
out: (id) => program.nodes[id]?.out ?? [],
|
|
104
|
-
selectByPort: (id, port) => {
|
|
105
|
-
const edges = program.nodes[id]?.out ?? [];
|
|
106
|
-
const byHandle = edges.find((e) => e.sourceHandle === port);
|
|
107
|
-
if (byHandle) return byHandle.to;
|
|
108
|
-
const byLabel = edges.find((e) => e.label === port);
|
|
109
|
-
return byLabel ? byLabel.to : null;
|
|
110
|
-
},
|
|
111
|
-
askNodeForSlot: (slot) => {
|
|
112
|
-
const want = slot.trim();
|
|
113
|
-
for (const node of Object.values(program.nodes)) {
|
|
114
|
-
if (node.type !== "ask") continue;
|
|
115
|
-
const s = typeof node.config.slot === "string" ? node.config.slot : node.id;
|
|
116
|
-
if (s.trim() === want) return node.id;
|
|
117
|
-
}
|
|
118
|
-
return void 0;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
var init_graph_model = __esm({
|
|
123
|
-
"src/runtime/graph/graph-model.ts"() {
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
98
|
// ../contracts/src/effect-stack.ts
|
|
128
99
|
var EffectStack;
|
|
129
100
|
var init_effect_stack = __esm({
|
|
@@ -1126,11 +1097,10 @@ var init_agent_config = __esm({
|
|
|
1126
1097
|
projectId: z.string().min(1),
|
|
1127
1098
|
// Human-readable label. No `agentType` enum on purpose.
|
|
1128
1099
|
name: z.string().min(1).max(128),
|
|
1129
|
-
//
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
//
|
|
1133
|
-
// honoured. Per-call override at call_start time is also allowed.
|
|
1100
|
+
// The agent's persona (the Behaviour page). For a FLOW agent the PUBLISHED value is the flow persona on every
|
|
1101
|
+
// transport (flowPersonaOf — voice boot and text replies alike; absent → DEFAULT_FLOW_PERSONA), read at call start as
|
|
1102
|
+
// a snapshot decision; mid-call edits are not honoured (burn-down G-46). A coded SDK agent's prompt is its code: this
|
|
1103
|
+
// field doesn't reach it. A call plan may still override the prompt per call.
|
|
1134
1104
|
systemPrompt: z.string().max(64e3).optional(),
|
|
1135
1105
|
// Routing/decision layer (Voiceflow "Instructions"): plain-language guidance the
|
|
1136
1106
|
// agent reads to decide WHEN to do things (vs systemPrompt = WHO it is). Injected
|
|
@@ -2089,12 +2059,143 @@ var init_call_result = __esm({
|
|
|
2089
2059
|
});
|
|
2090
2060
|
}
|
|
2091
2061
|
});
|
|
2062
|
+
function supportedZones() {
|
|
2063
|
+
if (!SUPPORTED_ZONES) {
|
|
2064
|
+
const intl = Intl;
|
|
2065
|
+
SUPPORTED_ZONES = new Set(intl.supportedValuesOf?.("timeZone") ?? []);
|
|
2066
|
+
}
|
|
2067
|
+
return SUPPORTED_ZONES;
|
|
2068
|
+
}
|
|
2069
|
+
function isIanaTimezone(tz) {
|
|
2070
|
+
if (tz === "UTC" || supportedZones().has(tz)) return true;
|
|
2071
|
+
if (!IANA_NAME.test(tz)) return false;
|
|
2072
|
+
try {
|
|
2073
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
2074
|
+
return true;
|
|
2075
|
+
} catch {
|
|
2076
|
+
return false;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
var BUDGET_SCOPE_KINDS, BudgetScopeKind, CREATABLE_BUDGET_SCOPE_KINDS, BUDGET_CHANNELS, BudgetScope, BudgetPeriodKind, BudgetEnforcement, BudgetInbound, BudgetOwner, DEFAULT_BUDGET_THRESHOLDS, BUDGET_MAX_USD, Thresholds, IANA_NAME, SUPPORTED_ZONES, Timezone, BudgetPeriodStatus, Budget, BudgetRefusalDetail, BillingNoticeKind, BillingNotice;
|
|
2080
|
+
var init_budgets = __esm({
|
|
2081
|
+
"../contracts/src/budgets.ts"() {
|
|
2082
|
+
BUDGET_SCOPE_KINDS = ["workspace", "agent", "api_key", "channel"];
|
|
2083
|
+
BudgetScopeKind = z.enum(BUDGET_SCOPE_KINDS);
|
|
2084
|
+
CREATABLE_BUDGET_SCOPE_KINDS = ["workspace", "api_key", "channel"];
|
|
2085
|
+
BUDGET_CHANNELS = ["voice", "sms", "email", "chat", "discord"];
|
|
2086
|
+
z.enum(BUDGET_CHANNELS);
|
|
2087
|
+
BudgetScope = z.object({
|
|
2088
|
+
kind: BudgetScopeKind,
|
|
2089
|
+
// null for the workspace; the API key's id, or the channel name.
|
|
2090
|
+
id: z.string().min(1).nullable()
|
|
2091
|
+
});
|
|
2092
|
+
BudgetPeriodKind = z.enum(["day", "month", "billing_period"]);
|
|
2093
|
+
BudgetEnforcement = z.enum(["soft", "hard"]);
|
|
2094
|
+
BudgetInbound = z.enum(["refuse", "answer"]);
|
|
2095
|
+
BudgetOwner = z.enum(["customer", "operator"]);
|
|
2096
|
+
DEFAULT_BUDGET_THRESHOLDS = [50, 80, 100];
|
|
2097
|
+
BUDGET_MAX_USD = 1e6;
|
|
2098
|
+
Thresholds = z.array(z.number().int().min(1).max(100)).max(5).refine((t) => new Set(t).size === t.length, "thresholds must be distinct").transform((t) => [...t].sort((a, b) => a - b));
|
|
2099
|
+
IANA_NAME = /^[A-Za-z]+(?:\/[A-Za-z0-9_+-]+)+$/;
|
|
2100
|
+
SUPPORTED_ZONES = null;
|
|
2101
|
+
Timezone = z.string().min(1).max(64).refine(isIanaTimezone, "use an IANA time zone name, like America/New_York or UTC");
|
|
2102
|
+
BudgetPeriodStatus = z.object({
|
|
2103
|
+
periodStart: z.string(),
|
|
2104
|
+
// When the budget resets.
|
|
2105
|
+
periodEnd: z.string(),
|
|
2106
|
+
// Charged usage this period.
|
|
2107
|
+
settledUsd: z.string(),
|
|
2108
|
+
// Reserved for calls in progress.
|
|
2109
|
+
inProgressUsd: z.string(),
|
|
2110
|
+
// settled + in progress — what the budget compares with its amount.
|
|
2111
|
+
spentUsd: z.string(),
|
|
2112
|
+
// spent ÷ amount, as a whole percent (may pass 100: calls in progress finish).
|
|
2113
|
+
percent: z.number().int().min(0),
|
|
2114
|
+
// A hard budget at its limit: new actions in its scope are refused until periodEnd.
|
|
2115
|
+
reached: z.boolean()
|
|
2116
|
+
});
|
|
2117
|
+
Budget = z.object({
|
|
2118
|
+
id: z.string(),
|
|
2119
|
+
scope: BudgetScope,
|
|
2120
|
+
period: BudgetPeriodKind,
|
|
2121
|
+
timezone: z.string(),
|
|
2122
|
+
amountUsd: z.string(),
|
|
2123
|
+
enforcement: BudgetEnforcement,
|
|
2124
|
+
thresholds: z.array(z.number().int()),
|
|
2125
|
+
notifyEmail: z.boolean(),
|
|
2126
|
+
notifyWebhook: z.boolean(),
|
|
2127
|
+
// Operator budgets are VoiceLayer's own safety limits: shown to the customer, read-only.
|
|
2128
|
+
owner: BudgetOwner,
|
|
2129
|
+
inbound: BudgetInbound,
|
|
2130
|
+
createdAt: z.string(),
|
|
2131
|
+
current: BudgetPeriodStatus
|
|
2132
|
+
});
|
|
2133
|
+
z.object({ budgets: z.array(Budget) });
|
|
2134
|
+
z.object({
|
|
2135
|
+
scope: z.object({ kind: z.enum(CREATABLE_BUDGET_SCOPE_KINDS), id: z.string().min(1).max(200).nullable().optional() }),
|
|
2136
|
+
period: BudgetPeriodKind,
|
|
2137
|
+
timezone: Timezone.default("UTC"),
|
|
2138
|
+
amountUsd: z.number().positive().max(BUDGET_MAX_USD),
|
|
2139
|
+
enforcement: BudgetEnforcement,
|
|
2140
|
+
thresholds: Thresholds.default([...DEFAULT_BUDGET_THRESHOLDS]),
|
|
2141
|
+
notifyEmail: z.boolean().default(true),
|
|
2142
|
+
notifyWebhook: z.boolean().default(true),
|
|
2143
|
+
// Hard budgets only; a customer budget refuses incoming calls unless told to answer them (MON-9).
|
|
2144
|
+
inbound: BudgetInbound.default("refuse")
|
|
2145
|
+
}).superRefine((b, ctx) => {
|
|
2146
|
+
const id = b.scope.id ?? null;
|
|
2147
|
+
if (b.scope.kind === "workspace" && id !== null) ctx.addIssue({ code: "custom", path: ["scope", "id"], message: "a workspace budget names no scope id" });
|
|
2148
|
+
if (b.scope.kind !== "workspace" && id === null) ctx.addIssue({ code: "custom", path: ["scope", "id"], message: "name the API key or channel" });
|
|
2149
|
+
if (b.scope.kind === "channel" && id !== null && !BUDGET_CHANNELS.includes(id)) {
|
|
2150
|
+
ctx.addIssue({ code: "custom", path: ["scope", "id"], message: `channel is one of ${BUDGET_CHANNELS.join(", ")}` });
|
|
2151
|
+
}
|
|
2152
|
+
});
|
|
2153
|
+
z.object({
|
|
2154
|
+
amountUsd: z.number().positive().max(BUDGET_MAX_USD),
|
|
2155
|
+
enforcement: BudgetEnforcement,
|
|
2156
|
+
thresholds: Thresholds,
|
|
2157
|
+
notifyEmail: z.boolean(),
|
|
2158
|
+
notifyWebhook: z.boolean(),
|
|
2159
|
+
inbound: BudgetInbound
|
|
2160
|
+
}).partial().refine((b) => Object.keys(b).length > 0, "nothing to change");
|
|
2161
|
+
BudgetRefusalDetail = z.object({
|
|
2162
|
+
budgetId: z.string(),
|
|
2163
|
+
scope: BudgetScope,
|
|
2164
|
+
period: BudgetPeriodKind,
|
|
2165
|
+
resetsAt: z.string()
|
|
2166
|
+
});
|
|
2167
|
+
BillingNoticeKind = z.enum([
|
|
2168
|
+
"balance_empty",
|
|
2169
|
+
"credit_limit_reached",
|
|
2170
|
+
"auto_topup_off",
|
|
2171
|
+
"wallet_frozen",
|
|
2172
|
+
"invoice_issued",
|
|
2173
|
+
"invoice_overdue",
|
|
2174
|
+
"workspace_paused",
|
|
2175
|
+
"workspace_restored",
|
|
2176
|
+
"budget_threshold",
|
|
2177
|
+
"budget_reached"
|
|
2178
|
+
]);
|
|
2179
|
+
BillingNotice = z.object({
|
|
2180
|
+
id: z.string(),
|
|
2181
|
+
kind: BillingNoticeKind,
|
|
2182
|
+
// The same words as the email's subject and first paragraph.
|
|
2183
|
+
title: z.string(),
|
|
2184
|
+
body: z.string(),
|
|
2185
|
+
// The budget a budget notice is about.
|
|
2186
|
+
budgetId: z.string().nullable(),
|
|
2187
|
+
createdAt: z.string()
|
|
2188
|
+
});
|
|
2189
|
+
z.object({ notices: z.array(BillingNotice) });
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2092
2192
|
var WEBHOOK_EVENT_TYPES, CallWebhookDirection, CallWebhookOutcome, CallWebhookAttributes, Party, CALL_ENDED_PAYLOAD_VERSION, CallEndedTranscript;
|
|
2093
2193
|
var init_webhook_events = __esm({
|
|
2094
2194
|
"../contracts/src/webhook-events.ts"() {
|
|
2095
2195
|
init_call_result();
|
|
2096
2196
|
init_call_outcome();
|
|
2097
|
-
|
|
2197
|
+
init_budgets();
|
|
2198
|
+
WEBHOOK_EVENT_TYPES = ["call.started", "call.ended", "recording.ready", "billing.budget_threshold", "billing.budget_reached"];
|
|
2098
2199
|
z.enum(WEBHOOK_EVENT_TYPES);
|
|
2099
2200
|
CallWebhookDirection = z.enum(["inbound", "outbound", "web"]);
|
|
2100
2201
|
CallWebhookOutcome = z.enum(["completed", "dropped", "failed", "no_answer", "other"]);
|
|
@@ -2153,6 +2254,19 @@ var init_webhook_events = __esm({
|
|
|
2153
2254
|
recordingUrl: z.string().url(),
|
|
2154
2255
|
status: z.enum(["starting", "active", "completed", "failed", "aborted"])
|
|
2155
2256
|
});
|
|
2257
|
+
z.object({
|
|
2258
|
+
budgetId: z.string(),
|
|
2259
|
+
scope: BudgetScope,
|
|
2260
|
+
period: BudgetPeriodKind,
|
|
2261
|
+
periodStart: z.string().datetime({ offset: true }),
|
|
2262
|
+
periodEnd: z.string().datetime({ offset: true }),
|
|
2263
|
+
amountUsd: z.string(),
|
|
2264
|
+
spentUsd: z.string(),
|
|
2265
|
+
inProgressUsd: z.string(),
|
|
2266
|
+
// The threshold passed (budget_reached: 100).
|
|
2267
|
+
thresholdPct: z.number().int().min(1).max(100),
|
|
2268
|
+
enforcement: BudgetEnforcement
|
|
2269
|
+
});
|
|
2156
2270
|
}
|
|
2157
2271
|
});
|
|
2158
2272
|
var PhoneNumberStatus, PhoneNumberProvider, PhoneNumberCapabilities, PhoneNumberRecord;
|
|
@@ -2227,10 +2341,172 @@ var init_project_limits = __esm({
|
|
|
2227
2341
|
});
|
|
2228
2342
|
|
|
2229
2343
|
// ../contracts/src/flow-ports.ts
|
|
2230
|
-
var INCOMPLETE_PORT_ID;
|
|
2344
|
+
var INCOMPLETE_PORT_ID, ASK_NEXT_PORT_ID, UNFILLED_PORT_ID, MISSING_PORT_ID;
|
|
2231
2345
|
var init_flow_ports = __esm({
|
|
2232
2346
|
"../contracts/src/flow-ports.ts"() {
|
|
2233
2347
|
INCOMPLETE_PORT_ID = "incomplete";
|
|
2348
|
+
ASK_NEXT_PORT_ID = "next";
|
|
2349
|
+
UNFILLED_PORT_ID = "unfilled";
|
|
2350
|
+
MISSING_PORT_ID = "missing";
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
|
|
2354
|
+
// ../contracts/src/flow-persona.ts
|
|
2355
|
+
function flowPersonaOf(config) {
|
|
2356
|
+
const prompt = config?.systemPrompt;
|
|
2357
|
+
return typeof prompt === "string" && prompt.trim() ? prompt : DEFAULT_FLOW_PERSONA;
|
|
2358
|
+
}
|
|
2359
|
+
function composeSystemPrompt(prompt, routingInstructions) {
|
|
2360
|
+
const routing = routingInstructions?.trim();
|
|
2361
|
+
return routing ? `${prompt}
|
|
2362
|
+
|
|
2363
|
+
# Routing
|
|
2364
|
+
${routing}` : prompt;
|
|
2365
|
+
}
|
|
2366
|
+
var DEFAULT_FLOW_PERSONA;
|
|
2367
|
+
var init_flow_persona = __esm({
|
|
2368
|
+
"../contracts/src/flow-persona.ts"() {
|
|
2369
|
+
DEFAULT_FLOW_PERSONA = "You are a helpful assistant. Keep replies short and natural, and follow the steps of the flow you have been given.";
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
|
|
2373
|
+
// ../contracts/src/question-text.ts
|
|
2374
|
+
function lastSentences(line) {
|
|
2375
|
+
const text = line.trim();
|
|
2376
|
+
if (!text) return [];
|
|
2377
|
+
const sentences = [];
|
|
2378
|
+
let last = 0;
|
|
2379
|
+
for (const m of text.matchAll(SENTENCE_END)) {
|
|
2380
|
+
const end = (m.index ?? 0) + m[0].length;
|
|
2381
|
+
sentences.push(text.slice(last, end));
|
|
2382
|
+
last = end;
|
|
2383
|
+
}
|
|
2384
|
+
if (last < text.length) sentences.push(text.slice(last));
|
|
2385
|
+
return sentences.slice(-2);
|
|
2386
|
+
}
|
|
2387
|
+
function isQuestion(line) {
|
|
2388
|
+
return lastSentences(line).some((s) => QUESTION_MARK.test(s));
|
|
2389
|
+
}
|
|
2390
|
+
var QUESTION_MARK, SENTENCE_END;
|
|
2391
|
+
var init_question_text = __esm({
|
|
2392
|
+
"../contracts/src/question-text.ts"() {
|
|
2393
|
+
QUESTION_MARK = /[??؟]/;
|
|
2394
|
+
SENTENCE_END = /[.!?…。!?؟]+["'”’)\]]*\s*/g;
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
|
|
2398
|
+
// ../contracts/src/flow-ask-lines.ts
|
|
2399
|
+
function norm(value) {
|
|
2400
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2401
|
+
}
|
|
2402
|
+
function fnv1a64(text) {
|
|
2403
|
+
let hash = FNV_OFFSET;
|
|
2404
|
+
for (const byte of new TextEncoder().encode(text)) {
|
|
2405
|
+
hash ^= BigInt(byte);
|
|
2406
|
+
hash = BigInt.asUintN(64, hash * FNV_PRIME);
|
|
2407
|
+
}
|
|
2408
|
+
return hash.toString(16).padStart(16, "0");
|
|
2409
|
+
}
|
|
2410
|
+
function askLinesBasis(input) {
|
|
2411
|
+
const canonical = JSON.stringify([
|
|
2412
|
+
["persona", norm(input.persona)],
|
|
2413
|
+
["routing", norm(input.routing)],
|
|
2414
|
+
["language", norm(input.language).toLowerCase()],
|
|
2415
|
+
["ask", norm(input.ask)],
|
|
2416
|
+
["instructions", norm(input.instructions)]
|
|
2417
|
+
]);
|
|
2418
|
+
return fnv1a64(canonical);
|
|
2419
|
+
}
|
|
2420
|
+
function isEnglishLanguage(language) {
|
|
2421
|
+
return norm(language).toLowerCase().startsWith("en");
|
|
2422
|
+
}
|
|
2423
|
+
function readAskGeneratedLines(value) {
|
|
2424
|
+
if (!value || typeof value !== "object") return null;
|
|
2425
|
+
const v = value;
|
|
2426
|
+
if (typeof v.basis !== "string" || !v.basis) return null;
|
|
2427
|
+
const reasks = Array.isArray(v.reasks) ? v.reasks.filter((x) => typeof x === "string" && x.trim() !== "") : [];
|
|
2428
|
+
return {
|
|
2429
|
+
reasks,
|
|
2430
|
+
basis: v.basis,
|
|
2431
|
+
...typeof v.skipOffer === "string" && v.skipOffer.trim() ? { skipOffer: v.skipOffer } : {},
|
|
2432
|
+
...typeof v.voicedAsk === "string" && v.voicedAsk.trim() ? { voicedAsk: v.voicedAsk } : {}
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function authorReasksOf(value) {
|
|
2436
|
+
return Array.isArray(value) ? value.filter((x) => typeof x === "string" && x.trim() !== "").map((x) => x.trim().slice(0, ASK_LINE_MAX_CHARS)).slice(0, ASK_REASKS_MAX) : [];
|
|
2437
|
+
}
|
|
2438
|
+
function defaultAskText(slot, nodeId) {
|
|
2439
|
+
const authored = slot === nodeId ? "" : slot.trim();
|
|
2440
|
+
const label = authored.replace(/[_-]+/g, " ");
|
|
2441
|
+
return label ? `Could you tell me your ${label}?` : "Could you tell me a bit more about what you need?";
|
|
2442
|
+
}
|
|
2443
|
+
var ASK_LINE_MAX_CHARS, ASK_INSTRUCTIONS_MAX_CHARS, ASK_REASKS_MAX, FNV_OFFSET, FNV_PRIME, DEFAULT_ASK_EXHAUSTED_MESSAGE;
|
|
2444
|
+
var init_flow_ask_lines = __esm({
|
|
2445
|
+
"../contracts/src/flow-ask-lines.ts"() {
|
|
2446
|
+
init_question_text();
|
|
2447
|
+
ASK_LINE_MAX_CHARS = 300;
|
|
2448
|
+
ASK_INSTRUCTIONS_MAX_CHARS = 1e3;
|
|
2449
|
+
ASK_REASKS_MAX = 3;
|
|
2450
|
+
FNV_OFFSET = BigInt("0xcbf29ce484222325");
|
|
2451
|
+
FNV_PRIME = BigInt("0x100000001b3");
|
|
2452
|
+
DEFAULT_ASK_EXHAUSTED_MESSAGE = "I wasn't able to get everything we need on this call, so let me have someone follow up with you. Thanks for your time.";
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
// ../contracts/src/flow-required-inputs.ts
|
|
2457
|
+
function templateFieldRefs(text) {
|
|
2458
|
+
if (typeof text !== "string" || text.indexOf("{{") === -1) return [];
|
|
2459
|
+
const out = [];
|
|
2460
|
+
for (const m of text.matchAll(TOKEN)) {
|
|
2461
|
+
const expr = (m[1] ?? "").trim();
|
|
2462
|
+
const name = expr.startsWith("slots.") ? expr.slice("slots.".length).trim() : expr;
|
|
2463
|
+
if (name && !out.includes(name)) out.push(name);
|
|
2464
|
+
}
|
|
2465
|
+
return out;
|
|
2466
|
+
}
|
|
2467
|
+
function stepInputNames(type, config) {
|
|
2468
|
+
const out = [];
|
|
2469
|
+
const add = (names) => {
|
|
2470
|
+
for (const n of names) if (!out.includes(n)) out.push(n);
|
|
2471
|
+
};
|
|
2472
|
+
if (type === "email") {
|
|
2473
|
+
for (const key of EMAIL_TEMPLATE_KEYS) add(templateFieldRefs(config[key]));
|
|
2474
|
+
return out;
|
|
2475
|
+
}
|
|
2476
|
+
if (type === "tool") {
|
|
2477
|
+
if (typeof config.toolRef === "string" && config.toolRef.trim()) return out;
|
|
2478
|
+
if (config.input && typeof config.input === "object") add(Object.keys(config.input));
|
|
2479
|
+
if (config.headers && typeof config.headers === "object") {
|
|
2480
|
+
for (const v of Object.values(config.headers)) add(templateFieldRefs(v));
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return out;
|
|
2484
|
+
}
|
|
2485
|
+
function requiredInputsFor(type, config, isRequired) {
|
|
2486
|
+
return stepInputNames(type, config).filter((n) => isRequired(n));
|
|
2487
|
+
}
|
|
2488
|
+
function requiredFieldNamesOf(nodes) {
|
|
2489
|
+
const out = /* @__PURE__ */ new Set();
|
|
2490
|
+
for (const node of nodes) {
|
|
2491
|
+
const config = node.config;
|
|
2492
|
+
if (node.type === "ask" && config.required !== false) {
|
|
2493
|
+
out.add((typeof config.slot === "string" && config.slot.trim() ? config.slot : node.id).trim());
|
|
2494
|
+
}
|
|
2495
|
+
if ((node.type === "playbook" || node.type === "gate") && Array.isArray(config.requiredFields)) {
|
|
2496
|
+
for (const f of config.requiredFields) if (typeof f === "string" && f.trim()) out.add(f.trim());
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return out;
|
|
2500
|
+
}
|
|
2501
|
+
function requiredFieldNames(graph) {
|
|
2502
|
+
return requiredFieldNamesOf(graph.nodes.map((n) => ({ id: n.id, type: n.type, config: n.data })));
|
|
2503
|
+
}
|
|
2504
|
+
var EMAIL_TEMPLATE_KEYS, TOKEN;
|
|
2505
|
+
var init_flow_required_inputs = __esm({
|
|
2506
|
+
"../contracts/src/flow-required-inputs.ts"() {
|
|
2507
|
+
init_flow_ports();
|
|
2508
|
+
EMAIL_TEMPLATE_KEYS = ["to", "subject", "body", "from", "replyTo", "fromName"];
|
|
2509
|
+
TOKEN = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
2234
2510
|
}
|
|
2235
2511
|
});
|
|
2236
2512
|
|
|
@@ -2333,6 +2609,15 @@ function asAdherence(value) {
|
|
|
2333
2609
|
function asStepEntry(value) {
|
|
2334
2610
|
return typeof value === "string" && STEP_ENTRIES.has(value) ? value : "auto";
|
|
2335
2611
|
}
|
|
2612
|
+
function asMaxAttempts(value) {
|
|
2613
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.min(6, Math.max(1, Math.round(value))) : ASK_DEFAULT_MAX_ATTEMPTS;
|
|
2614
|
+
}
|
|
2615
|
+
function asExhaustionPolicy(value) {
|
|
2616
|
+
return value === "fallback" || value === "end" ? value : "offer_skip";
|
|
2617
|
+
}
|
|
2618
|
+
function asRephrase(value) {
|
|
2619
|
+
return value === "all" || value === "never" ? value : "reasks";
|
|
2620
|
+
}
|
|
2336
2621
|
function branchesFor(data) {
|
|
2337
2622
|
return Array.isArray(data.branches) ? data.branches.filter((b) => b && typeof b.id === "string" && typeof b.label === "string").map((b) => ({
|
|
2338
2623
|
id: b.id,
|
|
@@ -2350,18 +2635,32 @@ function nodeConfigFor(node) {
|
|
|
2350
2635
|
case "say":
|
|
2351
2636
|
case "confirm":
|
|
2352
2637
|
return { text: typeof data.text === "string" ? data.text : "" };
|
|
2353
|
-
case "ask":
|
|
2638
|
+
case "ask": {
|
|
2639
|
+
const required = data.required !== false;
|
|
2640
|
+
const instructions = typeof data.instructions === "string" ? data.instructions.trim() : "";
|
|
2641
|
+
const reasks = authorReasksOf(data.reasks);
|
|
2642
|
+
const exhaustedMessage = typeof data.exhaustedMessage === "string" ? data.exhaustedMessage.trim() : "";
|
|
2354
2643
|
return {
|
|
2355
2644
|
slot: typeof data.slot === "string" ? data.slot : node.id,
|
|
2356
|
-
required
|
|
2645
|
+
required,
|
|
2357
2646
|
type: asFieldType(data.type),
|
|
2358
2647
|
historyPolicy: asHistoryPolicy(data.historyPolicy),
|
|
2359
2648
|
...typeof data.ask === "string" ? { ask: data.ask } : {},
|
|
2360
2649
|
...typeof data.pattern === "string" ? { pattern: data.pattern } : {},
|
|
2361
2650
|
...typeof data.min === "number" ? { min: data.min } : {},
|
|
2362
2651
|
...typeof data.max === "number" ? { max: data.max } : {},
|
|
2363
|
-
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {}
|
|
2652
|
+
...Array.isArray(data.enum) ? { enum: data.enum.filter((x) => typeof x === "string") } : {},
|
|
2653
|
+
// burn-down G-46/47/48. `maxAttempts` is ALWAYS emitted: its absence marks a program compiled before the
|
|
2654
|
+
// bounded ask, which the runtime keeps on today's loop until it is re-deployed.
|
|
2655
|
+
maxAttempts: asMaxAttempts(data.maxAttempts),
|
|
2656
|
+
...required ? { onExhausted: asExhaustionPolicy(data.onExhausted) } : {},
|
|
2657
|
+
...required && exhaustedMessage ? { exhaustedMessage } : {},
|
|
2658
|
+
rephrase: asRephrase(data.rephrase),
|
|
2659
|
+
...instructions ? { instructions: instructions.slice(0, ASK_INSTRUCTIONS_MAX_CHARS) } : {},
|
|
2660
|
+
// the author's own re-ask wording wins and is never regenerated
|
|
2661
|
+
...reasks.length ? { reasks } : {}
|
|
2364
2662
|
};
|
|
2663
|
+
}
|
|
2365
2664
|
case "llm":
|
|
2366
2665
|
return {
|
|
2367
2666
|
instructions: typeof data.instructions === "string" ? data.instructions : typeof data.text === "string" ? data.text : "",
|
|
@@ -2539,6 +2838,13 @@ function nodeConfigFor(node) {
|
|
|
2539
2838
|
return typeof data.label === "string" ? { label: data.label } : {};
|
|
2540
2839
|
}
|
|
2541
2840
|
}
|
|
2841
|
+
function portless(edge) {
|
|
2842
|
+
return {
|
|
2843
|
+
to: edge.to,
|
|
2844
|
+
...edge.condition !== void 0 ? { condition: edge.condition } : {},
|
|
2845
|
+
...edge.label !== void 0 ? { label: edge.label } : {}
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2542
2848
|
function compileFlowToProgram(graph, id = "flow") {
|
|
2543
2849
|
const stepNodes = graph.nodes.filter(
|
|
2544
2850
|
(n) => !CAPABILITY_NODE_TYPES.has(n.type) && !ANNOTATION_NODE_TYPES.has(n.type)
|
|
@@ -2565,13 +2871,28 @@ function compileFlowToProgram(graph, id = "flow") {
|
|
|
2565
2871
|
outBySource.set(edge.source, list);
|
|
2566
2872
|
indeg.set(edge.target, (indeg.get(edge.target) ?? 0) + 1);
|
|
2567
2873
|
}
|
|
2874
|
+
for (const node of stepNodes) {
|
|
2875
|
+
if (node.type !== "ask") continue;
|
|
2876
|
+
const list = outBySource.get(node.id);
|
|
2877
|
+
if (!list || !list.some((e) => e.sourceHandle === ASK_NEXT_PORT_ID)) continue;
|
|
2878
|
+
outBySource.set(
|
|
2879
|
+
node.id,
|
|
2880
|
+
list.filter((e) => e.sourceHandle !== void 0).map((e) => e.sourceHandle === ASK_NEXT_PORT_ID ? portless(e) : e)
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
const requiredFields = requiredFieldNames(graph);
|
|
2884
|
+
const isRequired = (f) => requiredFields.has(f.trim());
|
|
2568
2885
|
const nodes = {};
|
|
2569
2886
|
const unsupportedNodes = [];
|
|
2570
2887
|
for (const node of stepNodes) {
|
|
2888
|
+
const config = nodeConfigFor(node);
|
|
2889
|
+
if (node.type === "email" || node.type === "tool" && !config.toolRef) {
|
|
2890
|
+
config.requiredInputs = requiredInputsFor(node.type, config, isRequired);
|
|
2891
|
+
}
|
|
2571
2892
|
nodes[node.id] = {
|
|
2572
2893
|
id: node.id,
|
|
2573
2894
|
type: node.type,
|
|
2574
|
-
config
|
|
2895
|
+
config,
|
|
2575
2896
|
// Defensive: the FlowProgram schema caps out[] at 64 — slice rather than
|
|
2576
2897
|
// letting an over-wired node fail the read-side parse (which silently
|
|
2577
2898
|
// degrades the whole call to the generic base prompt).
|
|
@@ -2600,11 +2921,14 @@ function compileFlowToProgram(graph, id = "flow") {
|
|
|
2600
2921
|
}
|
|
2601
2922
|
};
|
|
2602
2923
|
}
|
|
2603
|
-
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, STEP_ENTRIES, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2924
|
+
var FIELD_TYPES, HTTP_METHODS, HISTORY_POLICIES, ADHERENCE_MODES, STEP_ENTRIES, ASK_DEFAULT_MAX_ATTEMPTS, UNSUPPORTED_NODE_TYPES, CAPABILITY_NODE_TYPES, ANNOTATION_NODE_TYPES;
|
|
2604
2925
|
var init_flow_compile = __esm({
|
|
2605
2926
|
"../contracts/src/flow-compile.ts"() {
|
|
2606
2927
|
init_flow_enrichment();
|
|
2607
2928
|
init_flow_edges();
|
|
2929
|
+
init_flow_ports();
|
|
2930
|
+
init_flow_ask_lines();
|
|
2931
|
+
init_flow_required_inputs();
|
|
2608
2932
|
FIELD_TYPES = [
|
|
2609
2933
|
"string",
|
|
2610
2934
|
"string[]",
|
|
@@ -2618,6 +2942,7 @@ var init_flow_compile = __esm({
|
|
|
2618
2942
|
HISTORY_POLICIES = /* @__PURE__ */ new Set(["full", "last-turn", "slots-only"]);
|
|
2619
2943
|
ADHERENCE_MODES = /* @__PURE__ */ new Set(["off", "observe", "enforce"]);
|
|
2620
2944
|
STEP_ENTRIES = /* @__PURE__ */ new Set(["auto", "speak", "listen"]);
|
|
2945
|
+
ASK_DEFAULT_MAX_ATTEMPTS = 3;
|
|
2621
2946
|
UNSUPPORTED_NODE_TYPES = /* @__PURE__ */ new Set([]);
|
|
2622
2947
|
CAPABILITY_NODE_TYPES = /* @__PURE__ */ new Set(["memory", "trigger", "speech"]);
|
|
2623
2948
|
ANNOTATION_NODE_TYPES = /* @__PURE__ */ new Set(["note"]);
|
|
@@ -2808,6 +3133,8 @@ var init_flow_validate = __esm({
|
|
|
2808
3133
|
init_flow_ports();
|
|
2809
3134
|
init_flow_edges();
|
|
2810
3135
|
init_flow_compile_check();
|
|
3136
|
+
init_flow_required_inputs();
|
|
3137
|
+
init_flow_ask_lines();
|
|
2811
3138
|
init_secret_headers();
|
|
2812
3139
|
init_network_address();
|
|
2813
3140
|
init_dispatch_metadata();
|
|
@@ -4335,6 +4662,7 @@ var init_monitoring = __esm({
|
|
|
4335
4662
|
var WalletRefusalCode, WalletMode, WalletInvoiceStatus, WalletInvoice, WalletEntryKind, WalletEntry, TOPUP_MIN_USD, TOPUP_MAX_USD;
|
|
4336
4663
|
var init_wallet = __esm({
|
|
4337
4664
|
"../contracts/src/wallet.ts"() {
|
|
4665
|
+
init_budgets();
|
|
4338
4666
|
WalletRefusalCode = z.enum([
|
|
4339
4667
|
// Prepaid balance (or an invoiced credit limit) cannot cover the action.
|
|
4340
4668
|
"insufficient_balance",
|
|
@@ -4344,14 +4672,18 @@ var init_wallet = __esm({
|
|
|
4344
4672
|
// still answered. Paying the invoice restores the workspace.
|
|
4345
4673
|
"account_suspended",
|
|
4346
4674
|
// The action's reservation already closed (e.g. a call ended or expired) — it cannot be re-admitted.
|
|
4347
|
-
"hold_closed"
|
|
4675
|
+
"hold_closed",
|
|
4676
|
+
// A hard budget (docs/specs/monetization LLD §8a) would be passed: new actions in its scope wait for its reset.
|
|
4677
|
+
"budget_exceeded"
|
|
4348
4678
|
]);
|
|
4349
4679
|
z.object({
|
|
4350
4680
|
error: WalletRefusalCode,
|
|
4351
4681
|
code: WalletRefusalCode,
|
|
4352
4682
|
availableUsd: z.string(),
|
|
4353
4683
|
needUsd: z.string(),
|
|
4354
|
-
message: z.string()
|
|
4684
|
+
message: z.string(),
|
|
4685
|
+
// budget_exceeded only: which budget, and when it resets.
|
|
4686
|
+
budget: BudgetRefusalDetail.optional()
|
|
4355
4687
|
});
|
|
4356
4688
|
WalletMode = z.enum(["prepaid", "invoiced"]);
|
|
4357
4689
|
z.object({
|
|
@@ -4392,7 +4724,7 @@ var init_wallet = __esm({
|
|
|
4392
4724
|
payUrl: z.string().nullable()
|
|
4393
4725
|
});
|
|
4394
4726
|
z.object({ invoices: z.array(WalletInvoice) });
|
|
4395
|
-
WalletEntryKind = z.enum(["grant", "topup", "usage", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
4727
|
+
WalletEntryKind = z.enum(["grant", "topup", "usage", "usage_correction", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
4396
4728
|
WalletEntry = z.object({
|
|
4397
4729
|
id: z.string(),
|
|
4398
4730
|
kind: WalletEntryKind,
|
|
@@ -4436,6 +4768,81 @@ var init_pipeline_keys = __esm({
|
|
|
4436
4768
|
});
|
|
4437
4769
|
}
|
|
4438
4770
|
});
|
|
4771
|
+
var METERING_SCHEMA_VERSION, METER_IDS, MeterId, KEY_OWNERS, READING_SOURCES, SUBJECT_KINDS, SubjectKind, MeteringSubject, DimValue, IdentityDims, Attributes, SERIES_PATTERN, SeriesName, Quantity, MeterReadingInput;
|
|
4772
|
+
var init_metering = __esm({
|
|
4773
|
+
"../contracts/src/metering.ts"() {
|
|
4774
|
+
METERING_SCHEMA_VERSION = 2;
|
|
4775
|
+
METER_IDS = [
|
|
4776
|
+
// A model's prompt tokens as the provider counts them (cached ones included); `cached_input_tokens` is the cached
|
|
4777
|
+
// subset and `reasoning_tokens` the reasoning subset of `output_tokens`. Rating prices the totals until it prices the
|
|
4778
|
+
// subsets on their own (monetization MON-14).
|
|
4779
|
+
"llm.input_tokens",
|
|
4780
|
+
"llm.cached_input_tokens",
|
|
4781
|
+
"llm.output_tokens",
|
|
4782
|
+
"llm.reasoning_tokens",
|
|
4783
|
+
"embedding.input_tokens",
|
|
4784
|
+
"realtime.audio_input_tokens",
|
|
4785
|
+
"realtime.audio_output_tokens",
|
|
4786
|
+
"realtime.text_input_tokens",
|
|
4787
|
+
"realtime.cached_input_tokens",
|
|
4788
|
+
"realtime.text_output_tokens",
|
|
4789
|
+
"stt.billed_seconds",
|
|
4790
|
+
"tts.characters",
|
|
4791
|
+
"call.connected_seconds",
|
|
4792
|
+
"telephony.carrier_seconds",
|
|
4793
|
+
"media.participant_seconds",
|
|
4794
|
+
"media.sip_seconds",
|
|
4795
|
+
"recording.seconds",
|
|
4796
|
+
"storage.byte_days",
|
|
4797
|
+
"message.segments",
|
|
4798
|
+
"email.messages",
|
|
4799
|
+
// One per billable text action — a text turn, or a single model action the API takes for a workspace (a QA review,
|
|
4800
|
+
// an evaluation, a planner call, a knowledge ingest): the per-message fee's basis.
|
|
4801
|
+
"text.turns",
|
|
4802
|
+
// Informational: number rental is a recurring fee on charge_cycles (🔒 B-21).
|
|
4803
|
+
"number.held_days",
|
|
4804
|
+
"provider_fee.micros"
|
|
4805
|
+
];
|
|
4806
|
+
MeterId = z.enum(METER_IDS);
|
|
4807
|
+
KEY_OWNERS = ["platform", "workspace_key", "workspace_plan", "workspace_brain", "self_hosted"];
|
|
4808
|
+
z.enum(KEY_OWNERS);
|
|
4809
|
+
READING_SOURCES = ["server_observed", "platform_wire", "platform_estimate", "customer_report"];
|
|
4810
|
+
z.enum(READING_SOURCES);
|
|
4811
|
+
SUBJECT_KINDS = ["call", "turn", "job", "message", "email", "number"];
|
|
4812
|
+
SubjectKind = z.enum(SUBJECT_KINDS);
|
|
4813
|
+
MeteringSubject = z.object({ kind: SubjectKind, id: z.string().min(1).max(200) });
|
|
4814
|
+
DimValue = z.string().max(200).nullable();
|
|
4815
|
+
IdentityDims = z.record(z.string(), DimValue);
|
|
4816
|
+
Attributes = z.object({
|
|
4817
|
+
served_model: z.string().max(200).nullable(),
|
|
4818
|
+
channel: z.string().max(64).nullable(),
|
|
4819
|
+
direction: z.string().max(32).nullable(),
|
|
4820
|
+
leg_kind: z.string().max(64).nullable(),
|
|
4821
|
+
country: z.string().max(8).nullable(),
|
|
4822
|
+
worker_owner: z.enum(["platform", "self_hosted", "unknown"]).nullable()
|
|
4823
|
+
});
|
|
4824
|
+
SERIES_PATTERN = /^(req|ws|srv):[A-Za-z0-9._:-]{1,60}$/;
|
|
4825
|
+
SeriesName = z.string().max(64).regex(SERIES_PATTERN);
|
|
4826
|
+
Quantity = z.string().regex(/^\d{1,14}(\.\d{1,6})?$/);
|
|
4827
|
+
MeterReadingInput = z.object({
|
|
4828
|
+
schemaVersion: z.literal(METERING_SCHEMA_VERSION),
|
|
4829
|
+
subject: MeteringSubject,
|
|
4830
|
+
meter: MeterId,
|
|
4831
|
+
identity: IdentityDims,
|
|
4832
|
+
series: SeriesName,
|
|
4833
|
+
quantity: Quantity,
|
|
4834
|
+
final: z.boolean(),
|
|
4835
|
+
void: z.literal(true).optional(),
|
|
4836
|
+
// The pre-send input estimate of a request that may never answer (LLD §4.1). A client never names a source: the
|
|
4837
|
+
// server derives it from the principal (§4.4), and this flag is the one thing only the producer knows. Honoured
|
|
4838
|
+
// only from the call's own worker token; it ranks below the request's wire count, which supersedes it.
|
|
4839
|
+
estimate: z.literal(true).optional(),
|
|
4840
|
+
occurredAt: z.string().datetime({ offset: true }),
|
|
4841
|
+
attributes: Attributes.partial().optional()
|
|
4842
|
+
}).strict();
|
|
4843
|
+
z.object({ readings: z.array(MeterReadingInput).min(1).max(200) }).strict();
|
|
4844
|
+
}
|
|
4845
|
+
});
|
|
4439
4846
|
|
|
4440
4847
|
// ../contracts/src/index.ts
|
|
4441
4848
|
var init_src = __esm({
|
|
@@ -4472,6 +4879,10 @@ var init_src = __esm({
|
|
|
4472
4879
|
init_flow();
|
|
4473
4880
|
init_flow_program();
|
|
4474
4881
|
init_flow_ports();
|
|
4882
|
+
init_flow_persona();
|
|
4883
|
+
init_flow_ask_lines();
|
|
4884
|
+
init_flow_required_inputs();
|
|
4885
|
+
init_question_text();
|
|
4475
4886
|
init_flow_validate();
|
|
4476
4887
|
init_flow_builtins();
|
|
4477
4888
|
init_compliance();
|
|
@@ -4500,16 +4911,73 @@ var init_src = __esm({
|
|
|
4500
4911
|
init_channel_connector();
|
|
4501
4912
|
init_monitoring();
|
|
4502
4913
|
init_wallet();
|
|
4914
|
+
init_budgets();
|
|
4503
4915
|
init_network_address();
|
|
4504
4916
|
init_dispatch_metadata();
|
|
4505
4917
|
init_pipeline_keys();
|
|
4918
|
+
init_metering();
|
|
4919
|
+
}
|
|
4920
|
+
});
|
|
4921
|
+
|
|
4922
|
+
// src/runtime/graph/graph-model.ts
|
|
4923
|
+
function buildGraphModel(program) {
|
|
4924
|
+
return {
|
|
4925
|
+
// the program's completion gate is the compiler's own subset of these (a gate's fields, else the required asks') —
|
|
4926
|
+
// read too, so a program carries one answer however it was produced
|
|
4927
|
+
requiredFields: /* @__PURE__ */ new Set([
|
|
4928
|
+
...requiredFieldNamesOf(Object.values(program.nodes)),
|
|
4929
|
+
...program.completionGate.requiredFields.map((f) => f.trim())
|
|
4930
|
+
]),
|
|
4931
|
+
entry: program.entry,
|
|
4932
|
+
node: (id) => program.nodes[id],
|
|
4933
|
+
out: (id) => program.nodes[id]?.out ?? [],
|
|
4934
|
+
selectByPort: (id, port) => {
|
|
4935
|
+
const edges = program.nodes[id]?.out ?? [];
|
|
4936
|
+
const byHandle = edges.find((e) => e.sourceHandle === port);
|
|
4937
|
+
if (byHandle) return byHandle.to;
|
|
4938
|
+
const byLabel = edges.find((e) => e.label === port);
|
|
4939
|
+
return byLabel ? byLabel.to : null;
|
|
4940
|
+
},
|
|
4941
|
+
askNodeForSlot: (slot) => {
|
|
4942
|
+
const want = slot.trim();
|
|
4943
|
+
for (const node of Object.values(program.nodes)) {
|
|
4944
|
+
if (node.type !== "ask") continue;
|
|
4945
|
+
const s = typeof node.config.slot === "string" ? node.config.slot : node.id;
|
|
4946
|
+
if (s.trim() === want) return node.id;
|
|
4947
|
+
}
|
|
4948
|
+
return void 0;
|
|
4949
|
+
}
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
var init_graph_model = __esm({
|
|
4953
|
+
"src/runtime/graph/graph-model.ts"() {
|
|
4954
|
+
init_src();
|
|
4955
|
+
}
|
|
4956
|
+
});
|
|
4957
|
+
|
|
4958
|
+
// src/runtime/graph/reask-templates.ts
|
|
4959
|
+
function templateReasks(ask) {
|
|
4960
|
+
const q = ask.trim();
|
|
4961
|
+
return [`${NOT_CAUGHT} ${q}`, `Let me ask that another way \u2014 ${q}`, `Just so I get it right: ${q}`];
|
|
4962
|
+
}
|
|
4963
|
+
function notCaught(line) {
|
|
4964
|
+
return `${NOT_CAUGHT} ${line.trim()}`;
|
|
4965
|
+
}
|
|
4966
|
+
function skipOfferTemplate(label) {
|
|
4967
|
+
const what = label.trim() ? `your ${label.trim()}` : "that detail";
|
|
4968
|
+
return `No problem if you don't have ${what} handy \u2014 shall we skip it for now?`;
|
|
4969
|
+
}
|
|
4970
|
+
var NOT_CAUGHT;
|
|
4971
|
+
var init_reask_templates = __esm({
|
|
4972
|
+
"src/runtime/graph/reask-templates.ts"() {
|
|
4973
|
+
NOT_CAUGHT = "Sorry, I didn't catch that.";
|
|
4506
4974
|
}
|
|
4507
4975
|
});
|
|
4508
4976
|
|
|
4509
4977
|
// src/runtime/graph/interpolate.ts
|
|
4510
4978
|
function interpolate(template, scope2) {
|
|
4511
4979
|
if (!template || template.indexOf("{{") === -1) return template;
|
|
4512
|
-
return template.replace(
|
|
4980
|
+
return template.replace(TOKEN2, (_match, expr) => {
|
|
4513
4981
|
const value = resolvePath(scope2, expr.trim());
|
|
4514
4982
|
return value === void 0 || value === null ? "" : String(value);
|
|
4515
4983
|
});
|
|
@@ -4534,10 +5002,10 @@ function hasSlot(slots, name) {
|
|
|
4534
5002
|
const v = resolvePath(slots, name.trim());
|
|
4535
5003
|
return v !== void 0 && v !== null && v !== "";
|
|
4536
5004
|
}
|
|
4537
|
-
var
|
|
5005
|
+
var TOKEN2;
|
|
4538
5006
|
var init_interpolate = __esm({
|
|
4539
5007
|
"src/runtime/graph/interpolate.ts"() {
|
|
4540
|
-
|
|
5008
|
+
TOKEN2 = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
4541
5009
|
}
|
|
4542
5010
|
});
|
|
4543
5011
|
|
|
@@ -4596,32 +5064,116 @@ function acceptsReasoningEffort(model2) {
|
|
|
4596
5064
|
const id = baseModelId(model2);
|
|
4597
5065
|
return isReasoningModel(model2) && !/^o1-(mini|preview)/.test(id) && !/-chat(-|$)/.test(id);
|
|
4598
5066
|
}
|
|
5067
|
+
function acceptsNoReasoningEffort(model2) {
|
|
5068
|
+
if (!acceptsReasoningEffort(model2))
|
|
5069
|
+
return false;
|
|
5070
|
+
const id = baseModelId(model2);
|
|
5071
|
+
if (/-pro\b/.test(id))
|
|
5072
|
+
return false;
|
|
5073
|
+
const gpt = /^gpt-(\d+)(?:\.(\d+))?/.exec(id);
|
|
5074
|
+
if (!gpt)
|
|
5075
|
+
return false;
|
|
5076
|
+
const major = Number(gpt[1]);
|
|
5077
|
+
const minor = gpt[2] !== void 0 ? Number(gpt[2]) : 0;
|
|
5078
|
+
return major > 5 || major === 5 && minor >= 1;
|
|
5079
|
+
}
|
|
5080
|
+
function forgetLearnedReasoningEfforts() {
|
|
5081
|
+
learnedEfforts.clear();
|
|
5082
|
+
}
|
|
5083
|
+
function chatReasoningEffort(model2, opts) {
|
|
5084
|
+
const learned = learnedEfforts.get(learnedKey(model2, opts.tools === true));
|
|
5085
|
+
if (learned !== void 0)
|
|
5086
|
+
return learned === "omit" ? void 0 : learned;
|
|
5087
|
+
if (!acceptsReasoningEffort(model2))
|
|
5088
|
+
return void 0;
|
|
5089
|
+
if (opts.tools === true && acceptsNoReasoningEffort(model2))
|
|
5090
|
+
return "none";
|
|
5091
|
+
return opts.requested;
|
|
5092
|
+
}
|
|
4599
5093
|
function chatCompletionParams(model2, input) {
|
|
5094
|
+
const effort = chatReasoningEffort(model2, {
|
|
5095
|
+
...input.tools !== void 0 ? { tools: input.tools } : {},
|
|
5096
|
+
...input.reasoningEffort !== void 0 ? { requested: input.reasoningEffort } : {}
|
|
5097
|
+
});
|
|
4600
5098
|
if (isReasoningModel(model2)) {
|
|
4601
5099
|
return {
|
|
4602
5100
|
...input.maxTokens !== void 0 ? { max_completion_tokens: Math.max(input.maxTokens, REASONING_MIN_COMPLETION_TOKENS) } : {},
|
|
4603
|
-
...
|
|
5101
|
+
...effort !== void 0 ? { reasoning_effort: effort } : {}
|
|
4604
5102
|
};
|
|
4605
5103
|
}
|
|
4606
5104
|
return {
|
|
5105
|
+
// only when a provider taught the process that this model (one we don't know as a reasoning model) takes an effort
|
|
5106
|
+
...effort !== void 0 ? { reasoning_effort: effort } : {},
|
|
4607
5107
|
...input.maxTokens !== void 0 ? { max_tokens: input.maxTokens } : {},
|
|
4608
5108
|
...input.temperature !== void 0 ? { temperature: input.temperature } : {},
|
|
4609
5109
|
...input.topP !== void 0 ? { top_p: input.topP } : {}
|
|
4610
5110
|
};
|
|
4611
5111
|
}
|
|
4612
|
-
function
|
|
5112
|
+
function acceptsSamplingParams(model2) {
|
|
5113
|
+
return !isReasoningModel(model2);
|
|
5114
|
+
}
|
|
5115
|
+
function providerErrorOf(err) {
|
|
5116
|
+
if (err === null || typeof err !== "object")
|
|
5117
|
+
return null;
|
|
4613
5118
|
const e = err;
|
|
4614
|
-
const
|
|
4615
|
-
|
|
5119
|
+
const body = e["body"] !== null && typeof e["body"] === "object" ? e["body"] : {};
|
|
5120
|
+
const pick = (k) => e[k] !== void 0 && e[k] !== null ? e[k] : body[k];
|
|
5121
|
+
const statusRaw = typeof e["status"] === "number" ? e["status"] : e["statusCode"];
|
|
5122
|
+
const status = typeof statusRaw === "number" && statusRaw > 0 ? statusRaw : null;
|
|
5123
|
+
const code = pick("code");
|
|
5124
|
+
const type = pick("type");
|
|
5125
|
+
const param = pick("param");
|
|
5126
|
+
const message = typeof body["message"] === "string" ? body["message"] : typeof e["message"] === "string" ? e["message"] : "";
|
|
5127
|
+
return {
|
|
5128
|
+
status,
|
|
5129
|
+
code: typeof code === "string" ? code : typeof type === "string" ? type : "",
|
|
5130
|
+
param: typeof param === "string" ? param : null,
|
|
5131
|
+
message
|
|
5132
|
+
};
|
|
5133
|
+
}
|
|
5134
|
+
function reasoningEffortRejectionOf(err) {
|
|
5135
|
+
const f = providerErrorOf(err);
|
|
5136
|
+
if (!f || f.status !== 400)
|
|
5137
|
+
return null;
|
|
5138
|
+
if (f.param !== "reasoning_effort" && !/reasoning[_ ]effort/i.test(f.message))
|
|
5139
|
+
return null;
|
|
5140
|
+
return { status: f.status, message: f.message, suggestsNone: /reasoning[_ ]effort[^.]*\bnone\b/i.test(f.message) };
|
|
5141
|
+
}
|
|
5142
|
+
function learnReasoningEffort(model2, shape, err) {
|
|
5143
|
+
const rejection = reasoningEffortRejectionOf(err);
|
|
5144
|
+
if (!rejection)
|
|
5145
|
+
return false;
|
|
5146
|
+
const next = shape.sent !== "none" && (rejection.suggestsNone || shape.sent === void 0) ? "none" : "omit";
|
|
5147
|
+
if ((next === "omit" ? void 0 : next) === shape.sent)
|
|
5148
|
+
return false;
|
|
5149
|
+
learnedEfforts.set(learnedKey(model2, shape.tools), next);
|
|
5150
|
+
return true;
|
|
5151
|
+
}
|
|
5152
|
+
async function withReasoningEffortRetry(shape, call) {
|
|
5153
|
+
const sent = chatReasoningEffort(shape.model, {
|
|
5154
|
+
tools: shape.tools,
|
|
5155
|
+
...shape.requested !== void 0 ? { requested: shape.requested } : {}
|
|
5156
|
+
});
|
|
5157
|
+
try {
|
|
5158
|
+
return await call();
|
|
5159
|
+
} catch (err) {
|
|
5160
|
+
if (!learnReasoningEffort(shape.model, { tools: shape.tools, sent }, err))
|
|
5161
|
+
throw err;
|
|
5162
|
+
return call();
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
function modelRejectionOf(err) {
|
|
5166
|
+
const f = providerErrorOf(err);
|
|
5167
|
+
const status = f?.status ?? null;
|
|
5168
|
+
if (!f || status === null || status !== 400 && status !== 403 && status !== 404)
|
|
4616
5169
|
return null;
|
|
4617
|
-
const
|
|
4618
|
-
const
|
|
4619
|
-
const rejected = REJECTION_CODES.has(code) || param !== null && MODEL_PARAMS.has(param);
|
|
5170
|
+
const { code, param } = f;
|
|
5171
|
+
const rejected = REJECTION_CODES.has(code) || param !== null && MODEL_PARAMS.has(param) || reasoningEffortRejectionOf(err) !== null;
|
|
4620
5172
|
if (!rejected)
|
|
4621
5173
|
return null;
|
|
4622
5174
|
if (status === 403 && code !== "model_not_found")
|
|
4623
5175
|
return null;
|
|
4624
|
-
return { status, code: code || "invalid_request_error", message:
|
|
5176
|
+
return { status, code: code || "invalid_request_error", message: f.message };
|
|
4625
5177
|
}
|
|
4626
5178
|
async function withModelFallback(opts) {
|
|
4627
5179
|
const fallbackModel = opts.fallbackModel ?? PLATFORM_DEFAULT_CHAT_MODEL;
|
|
@@ -4642,11 +5194,13 @@ async function withModelFallback(opts) {
|
|
|
4642
5194
|
return opts.call(fallbackModel);
|
|
4643
5195
|
}
|
|
4644
5196
|
}
|
|
4645
|
-
var PLATFORM_DEFAULT_CHAT_MODEL, REASONING_MIN_COMPLETION_TOKENS, REJECTION_CODES, MODEL_PARAMS, MODEL_REJECTION_TTL_MS, ModelRejectionCache;
|
|
5197
|
+
var PLATFORM_DEFAULT_CHAT_MODEL, REASONING_MIN_COMPLETION_TOKENS, learnedEfforts, learnedKey, REJECTION_CODES, MODEL_PARAMS, MODEL_REJECTION_TTL_MS, ModelRejectionCache;
|
|
4646
5198
|
var init_chat_params = __esm({
|
|
4647
5199
|
"../llm-client/dist/chat-params.js"() {
|
|
4648
5200
|
PLATFORM_DEFAULT_CHAT_MODEL = "gpt-4o-mini";
|
|
4649
5201
|
REASONING_MIN_COMPLETION_TOKENS = 2048;
|
|
5202
|
+
learnedEfforts = /* @__PURE__ */ new Map();
|
|
5203
|
+
learnedKey = (model2, tools) => `${baseModelId(model2)}|${tools ? "tools" : "plain"}`;
|
|
4650
5204
|
REJECTION_CODES = /* @__PURE__ */ new Set(["unsupported_parameter", "unsupported_value", "model_not_found"]);
|
|
4651
5205
|
MODEL_PARAMS = /* @__PURE__ */ new Set(["model", "max_tokens", "max_completion_tokens", "temperature", "top_p", "reasoning_effort"]);
|
|
4652
5206
|
MODEL_REJECTION_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -4674,6 +5228,30 @@ var init_chat_params = __esm({
|
|
|
4674
5228
|
};
|
|
4675
5229
|
}
|
|
4676
5230
|
});
|
|
5231
|
+
|
|
5232
|
+
// ../llm-client/dist/index.js
|
|
5233
|
+
var dist_exports = {};
|
|
5234
|
+
__export(dist_exports, {
|
|
5235
|
+
MODEL_REJECTION_TTL_MS: () => MODEL_REJECTION_TTL_MS,
|
|
5236
|
+
ModelRejectionCache: () => ModelRejectionCache,
|
|
5237
|
+
PLATFORM_DEFAULT_CHAT_MODEL: () => PLATFORM_DEFAULT_CHAT_MODEL,
|
|
5238
|
+
REASONING_MIN_COMPLETION_TOKENS: () => REASONING_MIN_COMPLETION_TOKENS,
|
|
5239
|
+
acceptsNoReasoningEffort: () => acceptsNoReasoningEffort,
|
|
5240
|
+
acceptsReasoningEffort: () => acceptsReasoningEffort,
|
|
5241
|
+
acceptsSamplingParams: () => acceptsSamplingParams,
|
|
5242
|
+
chatCompletionParams: () => chatCompletionParams,
|
|
5243
|
+
chatReasoningEffort: () => chatReasoningEffort,
|
|
5244
|
+
createChatClient: () => createChatClient,
|
|
5245
|
+
forgetLearnedReasoningEfforts: () => forgetLearnedReasoningEfforts,
|
|
5246
|
+
hasChatKey: () => hasChatKey,
|
|
5247
|
+
isReasoningModel: () => isReasoningModel,
|
|
5248
|
+
learnReasoningEffort: () => learnReasoningEffort,
|
|
5249
|
+
modelRejectionOf: () => modelRejectionOf,
|
|
5250
|
+
providerErrorOf: () => providerErrorOf,
|
|
5251
|
+
reasoningEffortRejectionOf: () => reasoningEffortRejectionOf,
|
|
5252
|
+
withModelFallback: () => withModelFallback,
|
|
5253
|
+
withReasoningEffortRetry: () => withReasoningEffortRetry
|
|
5254
|
+
});
|
|
4677
5255
|
function createChatClient(config = {}) {
|
|
4678
5256
|
return new OpenAI({
|
|
4679
5257
|
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
@@ -4681,6 +5259,9 @@ function createChatClient(config = {}) {
|
|
|
4681
5259
|
...config.timeoutMs ? { timeout: config.timeoutMs } : {}
|
|
4682
5260
|
});
|
|
4683
5261
|
}
|
|
5262
|
+
function hasChatKey(config = {}) {
|
|
5263
|
+
return Boolean(config.apiKey || process.env["OPENAI_API_KEY"]);
|
|
5264
|
+
}
|
|
4684
5265
|
var init_dist = __esm({
|
|
4685
5266
|
"../llm-client/dist/index.js"() {
|
|
4686
5267
|
init_chat_params();
|
|
@@ -5139,308 +5720,9 @@ var init_wrap = __esm({
|
|
|
5139
5720
|
"src/providers/wrap.ts"() {
|
|
5140
5721
|
}
|
|
5141
5722
|
});
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
try {
|
|
5146
|
-
return await import(spec);
|
|
5147
|
-
} catch {
|
|
5148
|
-
throw new Error(`${hint}: "${spec}" is not installed. Add it with: pnpm add ${spec}`);
|
|
5149
|
-
}
|
|
5150
|
-
}
|
|
5151
|
-
function withCreds(opts, creds2) {
|
|
5152
|
-
if (creds2?.apiKey) opts["apiKey"] = creds2.apiKey;
|
|
5153
|
-
if (creds2?.baseURL) opts["baseURL"] = creds2.baseURL;
|
|
5154
|
-
return opts;
|
|
5155
|
-
}
|
|
5156
|
-
var deepgram, openai, anthropic, cartesia, elevenlabs, assemblyai, google, silero, livekitTurn, connector;
|
|
5157
|
-
var init_providers2 = __esm({
|
|
5158
|
-
"src/providers/index.ts"() {
|
|
5159
|
-
init_ssrf();
|
|
5160
|
-
init_transport_callback();
|
|
5161
|
-
init_connector_llm();
|
|
5162
|
-
init_wrap();
|
|
5163
|
-
deepgram = {
|
|
5164
|
-
stt(options = {}) {
|
|
5165
|
-
return async () => {
|
|
5166
|
-
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.stt");
|
|
5167
|
-
return new dg.STT(
|
|
5168
|
-
withCreds(
|
|
5169
|
-
{ model: options.model ?? "nova-3", language: options.language ?? "en-US" },
|
|
5170
|
-
options
|
|
5171
|
-
)
|
|
5172
|
-
);
|
|
5173
|
-
};
|
|
5174
|
-
},
|
|
5175
|
-
tts(options = {}) {
|
|
5176
|
-
return async () => {
|
|
5177
|
-
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.tts");
|
|
5178
|
-
return new dg.TTS(
|
|
5179
|
-
withCreds({ model: options.model ?? "aura-2-harmonia-en" }, options)
|
|
5180
|
-
);
|
|
5181
|
-
};
|
|
5182
|
-
}
|
|
5183
|
-
};
|
|
5184
|
-
openai = {
|
|
5185
|
-
llm(options = {}) {
|
|
5186
|
-
return async () => {
|
|
5187
|
-
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.llm");
|
|
5188
|
-
return new oa.LLM(
|
|
5189
|
-
withCreds({ model: options.model ?? "gpt-4o-mini" }, options)
|
|
5190
|
-
);
|
|
5191
|
-
};
|
|
5192
|
-
},
|
|
5193
|
-
tts(options = {}) {
|
|
5194
|
-
return async () => {
|
|
5195
|
-
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.tts");
|
|
5196
|
-
const opts = { model: options.model ?? "tts-1" };
|
|
5197
|
-
if (options.voice) opts["voice"] = options.voice;
|
|
5198
|
-
if (options.instructions) opts["instructions"] = options.instructions;
|
|
5199
|
-
return new oa.TTS(withCreds(opts, options));
|
|
5200
|
-
};
|
|
5201
|
-
},
|
|
5202
|
-
// Speech-to-speech via the OpenAI Realtime API. Returns a RealtimeProvider
|
|
5203
|
-
// that the SDK uses in place of the stt/llm/tts pipeline.
|
|
5204
|
-
realtime(options = {}) {
|
|
5205
|
-
return async () => {
|
|
5206
|
-
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.realtime");
|
|
5207
|
-
const opts = {};
|
|
5208
|
-
if (options.model) opts["model"] = options.model;
|
|
5209
|
-
if (options.voice) opts["voice"] = options.voice;
|
|
5210
|
-
return new oa.realtime.RealtimeModel(withCreds(opts, options));
|
|
5211
|
-
};
|
|
5212
|
-
}
|
|
5213
|
-
};
|
|
5214
|
-
anthropic = {
|
|
5215
|
-
llm(options = {}) {
|
|
5216
|
-
return async () => {
|
|
5217
|
-
const an = await importOptional("@livekit/agents-plugin-anthropic", "anthropic.llm");
|
|
5218
|
-
return new an.LLM(
|
|
5219
|
-
withCreds({ model: options.model ?? "claude-sonnet-5-5" }, options)
|
|
5220
|
-
);
|
|
5221
|
-
};
|
|
5222
|
-
}
|
|
5223
|
-
};
|
|
5224
|
-
cartesia = {
|
|
5225
|
-
tts(options = {}) {
|
|
5226
|
-
return async () => {
|
|
5227
|
-
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.tts");
|
|
5228
|
-
return new ct.TTS(options);
|
|
5229
|
-
};
|
|
5230
|
-
},
|
|
5231
|
-
stt(options = {}) {
|
|
5232
|
-
return async () => {
|
|
5233
|
-
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.stt");
|
|
5234
|
-
const opts = {};
|
|
5235
|
-
if (options.model) opts["model"] = options.model;
|
|
5236
|
-
if (options.language) opts["language"] = options.language;
|
|
5237
|
-
return new ct.STT(withCreds(opts, options));
|
|
5238
|
-
};
|
|
5239
|
-
}
|
|
5240
|
-
};
|
|
5241
|
-
elevenlabs = {
|
|
5242
|
-
tts(options = {}) {
|
|
5243
|
-
return async () => {
|
|
5244
|
-
const el = await importOptional("@voicelayer/agents-plugin-elevenlabs", "elevenlabs.tts");
|
|
5245
|
-
const opts = {};
|
|
5246
|
-
if (options.voice) opts["voiceId"] = options.voice;
|
|
5247
|
-
if (options.model) opts["model"] = options.model;
|
|
5248
|
-
return new el.TTS(withCreds(opts, options));
|
|
5249
|
-
};
|
|
5250
|
-
}
|
|
5251
|
-
};
|
|
5252
|
-
assemblyai = {
|
|
5253
|
-
stt(options = {}) {
|
|
5254
|
-
return async () => {
|
|
5255
|
-
const aai = await importOptional("@voicelayer/agents-plugin-assemblyai", "assemblyai.stt");
|
|
5256
|
-
const opts = {};
|
|
5257
|
-
if (options.language) opts["language"] = options.language;
|
|
5258
|
-
return new aai.STT(withCreds(opts, options));
|
|
5259
|
-
};
|
|
5260
|
-
}
|
|
5261
|
-
};
|
|
5262
|
-
google = {
|
|
5263
|
-
llm(options = {}) {
|
|
5264
|
-
return async () => {
|
|
5265
|
-
const g = await importOptional("@voicelayer/agents-plugin-google", "google.llm");
|
|
5266
|
-
return new g.LLM(
|
|
5267
|
-
withCreds({ model: options.model ?? "gemini-3.8-flash" }, options)
|
|
5268
|
-
);
|
|
5269
|
-
};
|
|
5270
|
-
},
|
|
5271
|
-
realtime(options = {}) {
|
|
5272
|
-
return async () => {
|
|
5273
|
-
const g = await importOptional("@voicelayer/agents-plugin-google", "google.realtime");
|
|
5274
|
-
const opts = {};
|
|
5275
|
-
if (options.model) opts["model"] = options.model;
|
|
5276
|
-
if (options.voice) opts["voice"] = options.voice;
|
|
5277
|
-
return new g.beta.realtime.RealtimeModel(withCreds(opts, options));
|
|
5278
|
-
};
|
|
5279
|
-
}
|
|
5280
|
-
};
|
|
5281
|
-
silero = {
|
|
5282
|
-
vad() {
|
|
5283
|
-
return async () => {
|
|
5284
|
-
const sil = await importOptional("@voicelayer/agents-plugin-silero", "silero.vad");
|
|
5285
|
-
return await sil.VAD.load();
|
|
5286
|
-
};
|
|
5287
|
-
}
|
|
5288
|
-
};
|
|
5289
|
-
livekitTurn = {
|
|
5290
|
-
english() {
|
|
5291
|
-
return async () => {
|
|
5292
|
-
const lk = await importOptional("@voicelayer/agents-plugin-livekit", "livekitTurn.english");
|
|
5293
|
-
return new lk.turnDetector.EnglishModel();
|
|
5294
|
-
};
|
|
5295
|
-
},
|
|
5296
|
-
multilingual() {
|
|
5297
|
-
return async () => {
|
|
5298
|
-
const lk = await importOptional("@voicelayer/agents-plugin-livekit", "livekitTurn.multilingual");
|
|
5299
|
-
return new lk.turnDetector.MultilingualModel();
|
|
5300
|
-
};
|
|
5301
|
-
}
|
|
5302
|
-
};
|
|
5303
|
-
connector = {
|
|
5304
|
-
llm(config = {}) {
|
|
5305
|
-
return async (call) => {
|
|
5306
|
-
if (config.url) {
|
|
5307
|
-
await assertPublicHttpsUrl(
|
|
5308
|
-
config.url,
|
|
5309
|
-
config.allowHosts ? { allowHosts: config.allowHosts } : {}
|
|
5310
|
-
);
|
|
5311
|
-
return openai.llm({
|
|
5312
|
-
...config.model ? { model: config.model } : {},
|
|
5313
|
-
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
5314
|
-
baseURL: config.url
|
|
5315
|
-
})(call);
|
|
5316
|
-
}
|
|
5317
|
-
const transport = config.transport ?? (config.onQuery ? callbackTransport(config.onQuery) : void 0);
|
|
5318
|
-
if (!transport) {
|
|
5319
|
-
throw new Error("connector.llm requires one of: url, onQuery, or transport");
|
|
5320
|
-
}
|
|
5321
|
-
const projectId = typeof call.metadata["projectId"] === "string" ? call.metadata["projectId"] : void 0;
|
|
5322
|
-
const llmOptions = {
|
|
5323
|
-
...config.model ? { model: config.model } : {},
|
|
5324
|
-
...config.temperature !== void 0 ? { temperature: config.temperature } : {},
|
|
5325
|
-
...config.fallbackText ? { fallbackText: config.fallbackText } : {},
|
|
5326
|
-
...call.callId ? { callId: call.callId } : {},
|
|
5327
|
-
...projectId ? { projectId } : {}
|
|
5328
|
-
};
|
|
5329
|
-
return await createConnectorLLM(transport, llmOptions);
|
|
5330
|
-
};
|
|
5331
|
-
}
|
|
5332
|
-
};
|
|
5333
|
-
}
|
|
5334
|
-
});
|
|
5335
|
-
|
|
5336
|
-
// src/providers/registry.ts
|
|
5337
|
-
function entry(id, bits) {
|
|
5338
|
-
const identity = providerIdentity(id);
|
|
5339
|
-
if (!identity) throw new Error(`PROVIDER_REGISTRY: no provider identity in contracts for '${id}'`);
|
|
5340
|
-
return { name: identity.id, capabilities: identity.capabilities, ...bits };
|
|
5341
|
-
}
|
|
5342
|
-
function providerEntry(name) {
|
|
5343
|
-
if (!name) return void 0;
|
|
5344
|
-
return PROVIDER_REGISTRY[PROVIDER_ALIASES[name] ?? name];
|
|
5345
|
-
}
|
|
5346
|
-
function resolveStt(name, o) {
|
|
5347
|
-
return (providerEntry(name)?.stt ?? PROVIDER_REGISTRY["deepgram"].stt)(o);
|
|
5348
|
-
}
|
|
5349
|
-
function resolveTts(name, o) {
|
|
5350
|
-
return (providerEntry(name)?.tts ?? PROVIDER_REGISTRY["deepgram"].tts)(o);
|
|
5351
|
-
}
|
|
5352
|
-
function resolveLlm(name, o) {
|
|
5353
|
-
return (providerEntry(name)?.llm ?? PROVIDER_REGISTRY["openai"].llm)(withCurrentModel(name, o));
|
|
5354
|
-
}
|
|
5355
|
-
function resolveRealtime(name, o) {
|
|
5356
|
-
return (providerEntry(name)?.realtime ?? PROVIDER_REGISTRY["openai"].realtime)(withCurrentModel(name, o));
|
|
5357
|
-
}
|
|
5358
|
-
function withCurrentModel(name, o) {
|
|
5359
|
-
if (!name || !o.model) return o;
|
|
5360
|
-
const current = currentModelFor(name, o.model);
|
|
5361
|
-
if (!current.retired) return o;
|
|
5362
|
-
console.warn("[agent] the configured model is retired; running its replacement", {
|
|
5363
|
-
provider: name,
|
|
5364
|
-
model: current.retired,
|
|
5365
|
-
replacement: current.model
|
|
5366
|
-
});
|
|
5367
|
-
return { ...o, model: current.model };
|
|
5368
|
-
}
|
|
5369
|
-
var model, lang, voice, creds, PROVIDER_REGISTRY;
|
|
5370
|
-
var init_registry = __esm({
|
|
5371
|
-
"src/providers/registry.ts"() {
|
|
5372
|
-
init_providers2();
|
|
5373
|
-
init_src();
|
|
5374
|
-
model = (o) => o.model ? { model: o.model } : {};
|
|
5375
|
-
lang = (o) => o.language ? { language: o.language } : {};
|
|
5376
|
-
voice = (o) => o.voice ? { voice: o.voice } : {};
|
|
5377
|
-
creds = (o) => o.creds ?? {};
|
|
5378
|
-
PROVIDER_REGISTRY = {
|
|
5379
|
-
deepgram: entry("deepgram", {
|
|
5380
|
-
failureClass: "vendor-api",
|
|
5381
|
-
engines: ["livekit"],
|
|
5382
|
-
stt: (o) => deepgram.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
5383
|
-
// Deepgram folds the voice into the model id (aura-2-<name>-en): wire config
|
|
5384
|
-
// passes a voice, code config passes a model — both land as `model`.
|
|
5385
|
-
tts: (o) => {
|
|
5386
|
-
const m = o.voice ?? o.model;
|
|
5387
|
-
return deepgram.tts({ ...m ? { model: m } : {}, ...creds(o) });
|
|
5388
|
-
}
|
|
5389
|
-
}),
|
|
5390
|
-
openai: entry("openai", {
|
|
5391
|
-
failureClass: "vendor-api",
|
|
5392
|
-
engines: ["livekit", "text"],
|
|
5393
|
-
llm: (o) => openai.llm({ ...model(o), ...creds(o) }),
|
|
5394
|
-
tts: (o) => openai.tts({ ...voice(o), ...model(o), ...creds(o) }),
|
|
5395
|
-
realtime: (o) => openai.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
5396
|
-
}),
|
|
5397
|
-
anthropic: entry("anthropic", {
|
|
5398
|
-
failureClass: "vendor-api",
|
|
5399
|
-
engines: ["livekit", "text"],
|
|
5400
|
-
llm: (o) => anthropic.llm({ ...model(o), ...creds(o) })
|
|
5401
|
-
}),
|
|
5402
|
-
google: entry("google", {
|
|
5403
|
-
failureClass: "vendor-api",
|
|
5404
|
-
engines: ["livekit", "text"],
|
|
5405
|
-
llm: (o) => google.llm({ ...model(o), ...creds(o) }),
|
|
5406
|
-
realtime: (o) => google.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
5407
|
-
}),
|
|
5408
|
-
cartesia: entry("cartesia", {
|
|
5409
|
-
failureClass: "vendor-api",
|
|
5410
|
-
engines: ["livekit"],
|
|
5411
|
-
stt: (o) => cartesia.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
5412
|
-
tts: (o) => cartesia.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
5413
|
-
}),
|
|
5414
|
-
elevenlabs: entry("elevenlabs", {
|
|
5415
|
-
failureClass: "vendor-api",
|
|
5416
|
-
engines: ["livekit"],
|
|
5417
|
-
tts: (o) => elevenlabs.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
5418
|
-
}),
|
|
5419
|
-
assemblyai: entry("assemblyai", {
|
|
5420
|
-
failureClass: "vendor-api",
|
|
5421
|
-
engines: ["livekit"],
|
|
5422
|
-
stt: (o) => assemblyai.stt({ ...lang(o), ...creds(o) })
|
|
5423
|
-
// no model knob
|
|
5424
|
-
}),
|
|
5425
|
-
silero: entry("silero", {
|
|
5426
|
-
failureClass: "local",
|
|
5427
|
-
engines: ["livekit"],
|
|
5428
|
-
vad: () => silero.vad()
|
|
5429
|
-
})
|
|
5430
|
-
};
|
|
5431
|
-
}
|
|
5432
|
-
});
|
|
5433
|
-
|
|
5434
|
-
// src/providers/llm.ts
|
|
5435
|
-
var init_llm = __esm({
|
|
5436
|
-
"src/providers/llm.ts"() {
|
|
5437
|
-
init_openai_default();
|
|
5438
|
-
init_registry();
|
|
5439
|
-
}
|
|
5440
|
-
});
|
|
5441
|
-
function metricExportIntervalMs() {
|
|
5442
|
-
const raw = Number(process.env.OTEL_METRIC_EXPORT_INTERVAL);
|
|
5443
|
-
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_METRIC_EXPORT_INTERVAL_MS;
|
|
5723
|
+
function metricExportIntervalMs() {
|
|
5724
|
+
const raw = Number(process.env.OTEL_METRIC_EXPORT_INTERVAL);
|
|
5725
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_METRIC_EXPORT_INTERVAL_MS;
|
|
5444
5726
|
}
|
|
5445
5727
|
function start(opts) {
|
|
5446
5728
|
if (started)
|
|
@@ -5491,15 +5773,22 @@ function start(opts) {
|
|
|
5491
5773
|
]
|
|
5492
5774
|
});
|
|
5493
5775
|
sdk.start();
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5776
|
+
if (opts.signals === false)
|
|
5777
|
+
return;
|
|
5778
|
+
process.once("SIGTERM", () => void shutdownTelemetry().then(() => process.exit(0)));
|
|
5779
|
+
process.once("SIGINT", () => void shutdownTelemetry().then(() => process.exit(0)));
|
|
5780
|
+
}
|
|
5781
|
+
async function shutdownTelemetry() {
|
|
5782
|
+
const running = sdk;
|
|
5783
|
+
const logger = loggerProvider;
|
|
5784
|
+
sdk = null;
|
|
5785
|
+
loggerProvider = null;
|
|
5786
|
+
metricReader = null;
|
|
5787
|
+
try {
|
|
5788
|
+
await running?.shutdown();
|
|
5789
|
+
await logger?.shutdown();
|
|
5790
|
+
} catch {
|
|
5791
|
+
}
|
|
5503
5792
|
}
|
|
5504
5793
|
async function flushTelemetry() {
|
|
5505
5794
|
const flushes = [];
|
|
@@ -5815,61 +6104,614 @@ var init_metrics = __esm({
|
|
|
5815
6104
|
ttsChars: "vl.tts.chars",
|
|
5816
6105
|
sttSeconds: "vl.stt.seconds"
|
|
5817
6106
|
};
|
|
5818
|
-
_llmInputTokens = null;
|
|
5819
|
-
_llmOutputTokens = null;
|
|
5820
|
-
_ttsChars = null;
|
|
5821
|
-
_sttSeconds = null;
|
|
5822
|
-
_firstTokenMs = null;
|
|
5823
|
-
_ttsStartMs = null;
|
|
5824
|
-
_eouDelayMs = null;
|
|
5825
|
-
_eouTranscriptionDelayMs = null;
|
|
5826
|
-
_eouOnTurnCompletedDelayMs = null;
|
|
5827
|
-
_interruptionTotalMs = null;
|
|
5828
|
-
_interruptionPredictionMs = null;
|
|
5829
|
-
_interruptionDetectionDelayMs = null;
|
|
5830
|
-
_interruptionCount = null;
|
|
5831
|
-
_backchannelCount = null;
|
|
5832
|
-
_realtimeSessionDurationMs = null;
|
|
5833
|
-
_modelFallbacks = null;
|
|
6107
|
+
_llmInputTokens = null;
|
|
6108
|
+
_llmOutputTokens = null;
|
|
6109
|
+
_ttsChars = null;
|
|
6110
|
+
_sttSeconds = null;
|
|
6111
|
+
_firstTokenMs = null;
|
|
6112
|
+
_ttsStartMs = null;
|
|
6113
|
+
_eouDelayMs = null;
|
|
6114
|
+
_eouTranscriptionDelayMs = null;
|
|
6115
|
+
_eouOnTurnCompletedDelayMs = null;
|
|
6116
|
+
_interruptionTotalMs = null;
|
|
6117
|
+
_interruptionPredictionMs = null;
|
|
6118
|
+
_interruptionDetectionDelayMs = null;
|
|
6119
|
+
_interruptionCount = null;
|
|
6120
|
+
_backchannelCount = null;
|
|
6121
|
+
_realtimeSessionDurationMs = null;
|
|
6122
|
+
_modelFallbacks = null;
|
|
6123
|
+
}
|
|
6124
|
+
});
|
|
6125
|
+
function recordTurnLatencySpan(args) {
|
|
6126
|
+
if (!Number.isFinite(args.latencyMs) || args.latencyMs < 0)
|
|
6127
|
+
return;
|
|
6128
|
+
if (args.latencyMs === 0 && !args.allowZero)
|
|
6129
|
+
return;
|
|
6130
|
+
const attributes = {
|
|
6131
|
+
[ATTR.projectId]: args.projectId,
|
|
6132
|
+
[ATTR.callId]: args.callId,
|
|
6133
|
+
[LATENCY_STAGE_ATTR]: args.stage,
|
|
6134
|
+
[LATENCY_MS_ATTR]: args.latencyMs,
|
|
6135
|
+
[LATENCY_REALTIME_ATTR]: args.realtime ? 1 : 0
|
|
6136
|
+
};
|
|
6137
|
+
if (args.campaignId)
|
|
6138
|
+
attributes[ATTR.campaignId] = args.campaignId;
|
|
6139
|
+
trace.getTracer(TRACER_NAME2).startSpan(LATENCY_SPAN_NAME, { attributes }).end();
|
|
6140
|
+
}
|
|
6141
|
+
var TRACER_NAME2, LATENCY_SPAN_NAME, LATENCY_STAGE_ATTR, LATENCY_MS_ATTR, LATENCY_REALTIME_ATTR;
|
|
6142
|
+
var init_latency_span = __esm({
|
|
6143
|
+
"../observability/dist/latency-span.js"() {
|
|
6144
|
+
init_attributes();
|
|
6145
|
+
TRACER_NAME2 = "@voicelayer/observability";
|
|
6146
|
+
LATENCY_SPAN_NAME = "vl.turn_latency";
|
|
6147
|
+
LATENCY_STAGE_ATTR = "vl.stage";
|
|
6148
|
+
LATENCY_MS_ATTR = "vl.latency_ms";
|
|
6149
|
+
LATENCY_REALTIME_ATTR = "vl.realtime";
|
|
6150
|
+
}
|
|
6151
|
+
});
|
|
6152
|
+
|
|
6153
|
+
// ../observability/dist/index.js
|
|
6154
|
+
var init_dist2 = __esm({
|
|
6155
|
+
"../observability/dist/index.js"() {
|
|
6156
|
+
init_start();
|
|
6157
|
+
init_call_context();
|
|
6158
|
+
init_trace_propagation();
|
|
6159
|
+
init_attributes();
|
|
6160
|
+
init_metrics();
|
|
6161
|
+
init_latency_span();
|
|
6162
|
+
}
|
|
6163
|
+
});
|
|
6164
|
+
|
|
6165
|
+
// src/providers/resilient-llm.ts
|
|
6166
|
+
var resilient_llm_exports = {};
|
|
6167
|
+
__export(resilient_llm_exports, {
|
|
6168
|
+
LLM_APOLOGY: () => LLM_APOLOGY,
|
|
6169
|
+
ResilientLLM: () => ResilientLLM,
|
|
6170
|
+
isResilientLLM: () => isResilientLLM
|
|
6171
|
+
});
|
|
6172
|
+
function isResilientLLM(value) {
|
|
6173
|
+
return typeof value === "object" && value !== null && value[RESILIENT] === true;
|
|
6174
|
+
}
|
|
6175
|
+
function transient(error) {
|
|
6176
|
+
if (error instanceof APIStatusError) {
|
|
6177
|
+
const s = error.statusCode;
|
|
6178
|
+
return s === 408 || s === 429 || s < 0 || s >= 500;
|
|
6179
|
+
}
|
|
6180
|
+
return error instanceof APITimeoutError || error instanceof APIConnectionError;
|
|
6181
|
+
}
|
|
6182
|
+
var LLM_APOLOGY, RESILIENT, ResilientLLM, sameModel, ServedLLM, ResilientLLMStream;
|
|
6183
|
+
var init_resilient_llm = __esm({
|
|
6184
|
+
"src/providers/resilient-llm.ts"() {
|
|
6185
|
+
init_dist();
|
|
6186
|
+
init_dist2();
|
|
6187
|
+
LLM_APOLOGY = "Sorry, I'm having trouble right now. Could you give me a moment and say that again?";
|
|
6188
|
+
RESILIENT = /* @__PURE__ */ Symbol.for("voicelayer.resilientLLM");
|
|
6189
|
+
ResilientLLM = class extends llm.LLM {
|
|
6190
|
+
[RESILIENT] = true;
|
|
6191
|
+
#opts;
|
|
6192
|
+
#fallback = null;
|
|
6193
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
6194
|
+
/** Models the provider rejected on this call (a call's pipeline is built per call): later turns go straight to the fallback. */
|
|
6195
|
+
rejections = new ModelRejectionCache();
|
|
6196
|
+
constructor(opts) {
|
|
6197
|
+
super();
|
|
6198
|
+
this.#opts = opts;
|
|
6199
|
+
}
|
|
6200
|
+
label() {
|
|
6201
|
+
return this.#opts.primary.label();
|
|
6202
|
+
}
|
|
6203
|
+
get model() {
|
|
6204
|
+
return this.#opts.primary.model;
|
|
6205
|
+
}
|
|
6206
|
+
get provider() {
|
|
6207
|
+
return this.#opts.primary.provider;
|
|
6208
|
+
}
|
|
6209
|
+
get apology() {
|
|
6210
|
+
return this.#opts.apology ?? LLM_APOLOGY;
|
|
6211
|
+
}
|
|
6212
|
+
get reasoningParams() {
|
|
6213
|
+
return this.#opts.reasoningParams === true;
|
|
6214
|
+
}
|
|
6215
|
+
/** The fallback LLM, built on first need; null when there is none or it is the agent's own model. */
|
|
6216
|
+
fallbackLLM() {
|
|
6217
|
+
if (!this.#opts.fallback) return null;
|
|
6218
|
+
this.#fallback ??= this.#opts.fallback();
|
|
6219
|
+
return sameModel(this.#fallback.model, this.model) ? null : this.#fallback;
|
|
6220
|
+
}
|
|
6221
|
+
/** Hear about every recovery (and every apology). Returns the unsubscribe. */
|
|
6222
|
+
onIncident(listener) {
|
|
6223
|
+
this.#listeners.add(listener);
|
|
6224
|
+
return () => this.#listeners.delete(listener);
|
|
6225
|
+
}
|
|
6226
|
+
/** @internal */
|
|
6227
|
+
report(incident) {
|
|
6228
|
+
const log = incident.kind === "apology" ? console.error : console.warn;
|
|
6229
|
+
log(`[voicelayer] llm ${incident.kind.replace(/_/g, " ")}`, incident);
|
|
6230
|
+
if (incident.kind === "model_fallback") {
|
|
6231
|
+
const call = getCurrentCallContext();
|
|
6232
|
+
recordModelFallback({
|
|
6233
|
+
model: incident.model,
|
|
6234
|
+
fallbackModel: incident.fallbackModel,
|
|
6235
|
+
code: incident.code,
|
|
6236
|
+
surface: "voice",
|
|
6237
|
+
...call?.projectId ? { projectId: call.projectId } : {},
|
|
6238
|
+
...call?.agentId ? { agentId: call.agentId } : {}
|
|
6239
|
+
});
|
|
6240
|
+
}
|
|
6241
|
+
for (const listener of this.#listeners) {
|
|
6242
|
+
try {
|
|
6243
|
+
listener(incident);
|
|
6244
|
+
} catch {
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
chat(args) {
|
|
6249
|
+
return new ResilientLLMStream(this, args);
|
|
6250
|
+
}
|
|
6251
|
+
prewarm() {
|
|
6252
|
+
this.#opts.primary.prewarm();
|
|
6253
|
+
}
|
|
6254
|
+
async aclose() {
|
|
6255
|
+
await Promise.all([this.#opts.primary.aclose(), this.#fallback?.aclose()]);
|
|
6256
|
+
}
|
|
6257
|
+
/** @internal */
|
|
6258
|
+
get primary() {
|
|
6259
|
+
return this.#opts.primary;
|
|
6260
|
+
}
|
|
6261
|
+
};
|
|
6262
|
+
sameModel = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
6263
|
+
ServedLLM = class extends llm.LLM {
|
|
6264
|
+
constructor(owner, served) {
|
|
6265
|
+
super();
|
|
6266
|
+
this.owner = owner;
|
|
6267
|
+
this.served = served;
|
|
6268
|
+
}
|
|
6269
|
+
owner;
|
|
6270
|
+
served;
|
|
6271
|
+
label() {
|
|
6272
|
+
return this.owner.label();
|
|
6273
|
+
}
|
|
6274
|
+
get model() {
|
|
6275
|
+
return this.served();
|
|
6276
|
+
}
|
|
6277
|
+
get provider() {
|
|
6278
|
+
return this.owner.provider;
|
|
6279
|
+
}
|
|
6280
|
+
chat(args) {
|
|
6281
|
+
return this.owner.chat(args);
|
|
6282
|
+
}
|
|
6283
|
+
emit(event, ...args) {
|
|
6284
|
+
return this.owner.emit(event, ...args);
|
|
6285
|
+
}
|
|
6286
|
+
};
|
|
6287
|
+
ResilientLLMStream = class extends llm.LLMStream {
|
|
6288
|
+
#owner;
|
|
6289
|
+
#args;
|
|
6290
|
+
#conn;
|
|
6291
|
+
/** The model answering this stream — what its metrics report. */
|
|
6292
|
+
#served;
|
|
6293
|
+
#current = null;
|
|
6294
|
+
constructor(owner, args) {
|
|
6295
|
+
const conn = args.connOptions ?? DEFAULT_API_CONNECT_OPTIONS;
|
|
6296
|
+
const served = { model: owner.model };
|
|
6297
|
+
super(new ServedLLM(owner, () => served.model), {
|
|
6298
|
+
chatCtx: args.chatCtx,
|
|
6299
|
+
...args.toolCtx ? { toolCtx: args.toolCtx } : {},
|
|
6300
|
+
connOptions: { ...conn, maxRetry: 0 }
|
|
6301
|
+
});
|
|
6302
|
+
this.#owner = owner;
|
|
6303
|
+
this.#args = args;
|
|
6304
|
+
this.#conn = conn;
|
|
6305
|
+
this.#served = served;
|
|
6306
|
+
this.abortController.signal.addEventListener("abort", () => this.#current?.close());
|
|
6307
|
+
}
|
|
6308
|
+
get #hasTools() {
|
|
6309
|
+
return this.#args.toolCtx !== void 0 && Object.keys(this.#args.toolCtx).length > 0;
|
|
6310
|
+
}
|
|
6311
|
+
#extraKwargs(model2) {
|
|
6312
|
+
const base = this.#args.extraKwargs;
|
|
6313
|
+
if (!this.#owner.reasoningParams) return base;
|
|
6314
|
+
const effort = chatReasoningEffort(model2, { tools: this.#hasTools });
|
|
6315
|
+
if (effort === void 0) {
|
|
6316
|
+
if (!base || !("reasoning_effort" in base)) return base;
|
|
6317
|
+
const { reasoning_effort: _dropped, ...rest } = base;
|
|
6318
|
+
return rest;
|
|
6319
|
+
}
|
|
6320
|
+
return { ...base, reasoning_effort: effort };
|
|
6321
|
+
}
|
|
6322
|
+
/** One request on `target`, forwarding its chunks. Its failure is the inner LLM's 'error' event. */
|
|
6323
|
+
async #attempt(target) {
|
|
6324
|
+
let failure2 = null;
|
|
6325
|
+
const onError = (ev) => {
|
|
6326
|
+
failure2 ??= ev.error;
|
|
6327
|
+
};
|
|
6328
|
+
target.on("error", onError);
|
|
6329
|
+
let started2 = false;
|
|
6330
|
+
try {
|
|
6331
|
+
const extraKwargs = this.#extraKwargs(target.model);
|
|
6332
|
+
const stream = target.chat({
|
|
6333
|
+
...this.#args,
|
|
6334
|
+
connOptions: { ...this.#conn, maxRetry: 0 },
|
|
6335
|
+
...extraKwargs !== void 0 ? { extraKwargs } : {}
|
|
6336
|
+
});
|
|
6337
|
+
this.#current = stream;
|
|
6338
|
+
for await (const chunk of stream) {
|
|
6339
|
+
if (this.abortController.signal.aborted) break;
|
|
6340
|
+
started2 = true;
|
|
6341
|
+
this.queue.put(chunk);
|
|
6342
|
+
}
|
|
6343
|
+
} catch (err) {
|
|
6344
|
+
failure2 ??= err instanceof Error ? err : new Error(String(err));
|
|
6345
|
+
} finally {
|
|
6346
|
+
target.off("error", onError);
|
|
6347
|
+
this.#current = null;
|
|
6348
|
+
}
|
|
6349
|
+
return failure2 ? { ok: false, error: failure2, started: started2 } : { ok: true };
|
|
6350
|
+
}
|
|
6351
|
+
/** Run `target` until it answers, or a failure that retrying it won't fix. */
|
|
6352
|
+
async #run(target) {
|
|
6353
|
+
let effortRetried = false;
|
|
6354
|
+
for (let retries = 0; ; ) {
|
|
6355
|
+
const sent = this.#owner.reasoningParams ? chatReasoningEffort(target.model, { tools: this.#hasTools }) : void 0;
|
|
6356
|
+
const result = await this.#attempt(target);
|
|
6357
|
+
if (result.ok || result.started || this.abortController.signal.aborted) return result;
|
|
6358
|
+
if (this.#owner.reasoningParams && !effortRetried && learnReasoningEffort(target.model, { tools: this.#hasTools, sent }, result.error)) {
|
|
6359
|
+
effortRetried = true;
|
|
6360
|
+
this.#owner.report({
|
|
6361
|
+
kind: "reasoning_effort_adapted",
|
|
6362
|
+
model: target.model,
|
|
6363
|
+
message: providerErrorOf(result.error)?.message ?? result.error.message
|
|
6364
|
+
});
|
|
6365
|
+
continue;
|
|
6366
|
+
}
|
|
6367
|
+
if (!transient(result.error) || retries >= this.#conn.maxRetry) return result;
|
|
6368
|
+
const wait = intervalForRetry(this.#conn, retries);
|
|
6369
|
+
retries += 1;
|
|
6370
|
+
if (wait > 0) await new Promise((r) => setTimeout(r, wait));
|
|
6371
|
+
if (this.abortController.signal.aborted) return result;
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
async run() {
|
|
6375
|
+
const owner = this.#owner;
|
|
6376
|
+
const model2 = owner.model;
|
|
6377
|
+
const fallback = owner.fallbackLLM();
|
|
6378
|
+
const known = fallback ? owner.rejections.get(model2) : null;
|
|
6379
|
+
let failure2;
|
|
6380
|
+
if (known && fallback) {
|
|
6381
|
+
owner.report({ kind: "model_fallback", model: model2, fallbackModel: fallback.model, code: known.code, message: known.message, cached: true });
|
|
6382
|
+
failure2 = new Error(known.message);
|
|
6383
|
+
} else {
|
|
6384
|
+
const first = await this.#run(owner.primary);
|
|
6385
|
+
if (first.ok || first.started || this.abortController.signal.aborted) return;
|
|
6386
|
+
failure2 = first.error;
|
|
6387
|
+
const rejection = modelRejectionOf(first.error);
|
|
6388
|
+
if (rejection) owner.rejections.set(model2, rejection);
|
|
6389
|
+
if (fallback) {
|
|
6390
|
+
const f = providerErrorOf(first.error);
|
|
6391
|
+
owner.report({
|
|
6392
|
+
kind: "model_fallback",
|
|
6393
|
+
model: model2,
|
|
6394
|
+
fallbackModel: fallback.model,
|
|
6395
|
+
code: rejection?.code ?? (f?.status ? String(f.status) : "error"),
|
|
6396
|
+
message: f?.message || first.error.message,
|
|
6397
|
+
cached: false
|
|
6398
|
+
});
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
if (fallback) {
|
|
6402
|
+
this.#served.model = fallback.model;
|
|
6403
|
+
const second = await this.#run(fallback);
|
|
6404
|
+
if (second.ok || second.started || this.abortController.signal.aborted) return;
|
|
6405
|
+
failure2 = second.error;
|
|
6406
|
+
}
|
|
6407
|
+
owner.report({ kind: "apology", model: this.#served.model, message: providerErrorOf(failure2)?.message || failure2.message });
|
|
6408
|
+
this.queue.put({ id: `vl-apology-${Date.now()}`, delta: { role: "assistant", content: owner.apology } });
|
|
6409
|
+
}
|
|
6410
|
+
};
|
|
6411
|
+
}
|
|
6412
|
+
});
|
|
6413
|
+
|
|
6414
|
+
// src/providers/index.ts
|
|
6415
|
+
async function importOptional(spec, hint) {
|
|
6416
|
+
try {
|
|
6417
|
+
return await import(spec);
|
|
6418
|
+
} catch {
|
|
6419
|
+
throw new Error(`${hint}: "${spec}" is not installed. Add it with: pnpm add ${spec}`);
|
|
6420
|
+
}
|
|
6421
|
+
}
|
|
6422
|
+
function withCreds(opts, creds2) {
|
|
6423
|
+
if (creds2?.apiKey) opts["apiKey"] = creds2.apiKey;
|
|
6424
|
+
if (creds2?.baseURL) opts["baseURL"] = creds2.baseURL;
|
|
6425
|
+
return opts;
|
|
6426
|
+
}
|
|
6427
|
+
var deepgram, openai, anthropic, cartesia, elevenlabs, assemblyai, google, silero, livekitTurn, connector;
|
|
6428
|
+
var init_providers2 = __esm({
|
|
6429
|
+
"src/providers/index.ts"() {
|
|
6430
|
+
init_ssrf();
|
|
6431
|
+
init_transport_callback();
|
|
6432
|
+
init_connector_llm();
|
|
6433
|
+
init_wrap();
|
|
6434
|
+
deepgram = {
|
|
6435
|
+
stt(options = {}) {
|
|
6436
|
+
return async () => {
|
|
6437
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.stt");
|
|
6438
|
+
return new dg.STT(
|
|
6439
|
+
withCreds(
|
|
6440
|
+
{ model: options.model ?? "nova-3", language: options.language ?? "en-US" },
|
|
6441
|
+
options
|
|
6442
|
+
)
|
|
6443
|
+
);
|
|
6444
|
+
};
|
|
6445
|
+
},
|
|
6446
|
+
tts(options = {}) {
|
|
6447
|
+
return async () => {
|
|
6448
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.tts");
|
|
6449
|
+
return new dg.TTS(
|
|
6450
|
+
withCreds({ model: options.model ?? "aura-2-harmonia-en" }, options)
|
|
6451
|
+
);
|
|
6452
|
+
};
|
|
6453
|
+
}
|
|
6454
|
+
};
|
|
6455
|
+
openai = {
|
|
6456
|
+
llm(options = {}) {
|
|
6457
|
+
return async () => {
|
|
6458
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.llm");
|
|
6459
|
+
const { ResilientLLM: ResilientLLM2 } = await Promise.resolve().then(() => (init_resilient_llm(), resilient_llm_exports));
|
|
6460
|
+
const { PLATFORM_DEFAULT_CHAT_MODEL: PLATFORM_DEFAULT_CHAT_MODEL2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
6461
|
+
return new ResilientLLM2({
|
|
6462
|
+
primary: new oa.LLM(withCreds({ model: options.model ?? "gpt-4o-mini" }, options)),
|
|
6463
|
+
fallback: () => new oa.LLM(withCreds({ model: PLATFORM_DEFAULT_CHAT_MODEL2 }, options)),
|
|
6464
|
+
reasoningParams: true
|
|
6465
|
+
});
|
|
6466
|
+
};
|
|
6467
|
+
},
|
|
6468
|
+
tts(options = {}) {
|
|
6469
|
+
return async () => {
|
|
6470
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.tts");
|
|
6471
|
+
const opts = { model: options.model ?? "tts-1" };
|
|
6472
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
6473
|
+
if (options.instructions) opts["instructions"] = options.instructions;
|
|
6474
|
+
return new oa.TTS(withCreds(opts, options));
|
|
6475
|
+
};
|
|
6476
|
+
},
|
|
6477
|
+
// Speech-to-speech via the OpenAI Realtime API. Returns a RealtimeProvider
|
|
6478
|
+
// that the SDK uses in place of the stt/llm/tts pipeline.
|
|
6479
|
+
realtime(options = {}) {
|
|
6480
|
+
return async () => {
|
|
6481
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.realtime");
|
|
6482
|
+
const opts = {};
|
|
6483
|
+
if (options.model) opts["model"] = options.model;
|
|
6484
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
6485
|
+
return new oa.realtime.RealtimeModel(withCreds(opts, options));
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6488
|
+
};
|
|
6489
|
+
anthropic = {
|
|
6490
|
+
llm(options = {}) {
|
|
6491
|
+
return async () => {
|
|
6492
|
+
const an = await importOptional("@livekit/agents-plugin-anthropic", "anthropic.llm");
|
|
6493
|
+
return new an.LLM(
|
|
6494
|
+
withCreds({ model: options.model ?? "claude-sonnet-5-5" }, options)
|
|
6495
|
+
);
|
|
6496
|
+
};
|
|
6497
|
+
}
|
|
6498
|
+
};
|
|
6499
|
+
cartesia = {
|
|
6500
|
+
tts(options = {}) {
|
|
6501
|
+
return async () => {
|
|
6502
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.tts");
|
|
6503
|
+
return new ct.TTS(options);
|
|
6504
|
+
};
|
|
6505
|
+
},
|
|
6506
|
+
stt(options = {}) {
|
|
6507
|
+
return async () => {
|
|
6508
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.stt");
|
|
6509
|
+
const opts = {};
|
|
6510
|
+
if (options.model) opts["model"] = options.model;
|
|
6511
|
+
if (options.language) opts["language"] = options.language;
|
|
6512
|
+
return new ct.STT(withCreds(opts, options));
|
|
6513
|
+
};
|
|
6514
|
+
}
|
|
6515
|
+
};
|
|
6516
|
+
elevenlabs = {
|
|
6517
|
+
tts(options = {}) {
|
|
6518
|
+
return async () => {
|
|
6519
|
+
const el = await importOptional("@voicelayer/agents-plugin-elevenlabs", "elevenlabs.tts");
|
|
6520
|
+
const opts = {};
|
|
6521
|
+
if (options.voice) opts["voiceId"] = options.voice;
|
|
6522
|
+
if (options.model) opts["model"] = options.model;
|
|
6523
|
+
return new el.TTS(withCreds(opts, options));
|
|
6524
|
+
};
|
|
6525
|
+
}
|
|
6526
|
+
};
|
|
6527
|
+
assemblyai = {
|
|
6528
|
+
stt(options = {}) {
|
|
6529
|
+
return async () => {
|
|
6530
|
+
const aai = await importOptional("@voicelayer/agents-plugin-assemblyai", "assemblyai.stt");
|
|
6531
|
+
const opts = {};
|
|
6532
|
+
if (options.language) opts["language"] = options.language;
|
|
6533
|
+
return new aai.STT(withCreds(opts, options));
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
};
|
|
6537
|
+
google = {
|
|
6538
|
+
llm(options = {}) {
|
|
6539
|
+
return async () => {
|
|
6540
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.llm");
|
|
6541
|
+
const { ResilientLLM: ResilientLLM2 } = await Promise.resolve().then(() => (init_resilient_llm(), resilient_llm_exports));
|
|
6542
|
+
return new ResilientLLM2({
|
|
6543
|
+
primary: new g.LLM(withCreds({ model: options.model ?? "gemini-3.8-flash" }, options))
|
|
6544
|
+
});
|
|
6545
|
+
};
|
|
6546
|
+
},
|
|
6547
|
+
realtime(options = {}) {
|
|
6548
|
+
return async () => {
|
|
6549
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.realtime");
|
|
6550
|
+
const opts = {};
|
|
6551
|
+
if (options.model) opts["model"] = options.model;
|
|
6552
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
6553
|
+
return new g.beta.realtime.RealtimeModel(withCreds(opts, options));
|
|
6554
|
+
};
|
|
6555
|
+
}
|
|
6556
|
+
};
|
|
6557
|
+
silero = {
|
|
6558
|
+
vad() {
|
|
6559
|
+
return async () => {
|
|
6560
|
+
const sil = await importOptional("@voicelayer/agents-plugin-silero", "silero.vad");
|
|
6561
|
+
return await sil.VAD.load();
|
|
6562
|
+
};
|
|
6563
|
+
}
|
|
6564
|
+
};
|
|
6565
|
+
livekitTurn = {
|
|
6566
|
+
english() {
|
|
6567
|
+
return async () => {
|
|
6568
|
+
const lk = await importOptional("@voicelayer/agents-plugin-livekit", "livekitTurn.english");
|
|
6569
|
+
return new lk.turnDetector.EnglishModel();
|
|
6570
|
+
};
|
|
6571
|
+
},
|
|
6572
|
+
multilingual() {
|
|
6573
|
+
return async () => {
|
|
6574
|
+
const lk = await importOptional("@voicelayer/agents-plugin-livekit", "livekitTurn.multilingual");
|
|
6575
|
+
return new lk.turnDetector.MultilingualModel();
|
|
6576
|
+
};
|
|
6577
|
+
}
|
|
6578
|
+
};
|
|
6579
|
+
connector = {
|
|
6580
|
+
llm(config = {}) {
|
|
6581
|
+
return async (call) => {
|
|
6582
|
+
if (config.url) {
|
|
6583
|
+
await assertPublicHttpsUrl(
|
|
6584
|
+
config.url,
|
|
6585
|
+
config.allowHosts ? { allowHosts: config.allowHosts } : {}
|
|
6586
|
+
);
|
|
6587
|
+
return openai.llm({
|
|
6588
|
+
...config.model ? { model: config.model } : {},
|
|
6589
|
+
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
6590
|
+
baseURL: config.url
|
|
6591
|
+
})(call);
|
|
6592
|
+
}
|
|
6593
|
+
const transport = config.transport ?? (config.onQuery ? callbackTransport(config.onQuery) : void 0);
|
|
6594
|
+
if (!transport) {
|
|
6595
|
+
throw new Error("connector.llm requires one of: url, onQuery, or transport");
|
|
6596
|
+
}
|
|
6597
|
+
const projectId = typeof call.metadata["projectId"] === "string" ? call.metadata["projectId"] : void 0;
|
|
6598
|
+
const llmOptions = {
|
|
6599
|
+
...config.model ? { model: config.model } : {},
|
|
6600
|
+
...config.temperature !== void 0 ? { temperature: config.temperature } : {},
|
|
6601
|
+
...config.fallbackText ? { fallbackText: config.fallbackText } : {},
|
|
6602
|
+
...call.callId ? { callId: call.callId } : {},
|
|
6603
|
+
...projectId ? { projectId } : {}
|
|
6604
|
+
};
|
|
6605
|
+
return await createConnectorLLM(transport, llmOptions);
|
|
6606
|
+
};
|
|
6607
|
+
}
|
|
6608
|
+
};
|
|
5834
6609
|
}
|
|
5835
6610
|
});
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
[ATTR.projectId]: args.projectId,
|
|
5843
|
-
[ATTR.callId]: args.callId,
|
|
5844
|
-
[LATENCY_STAGE_ATTR]: args.stage,
|
|
5845
|
-
[LATENCY_MS_ATTR]: args.latencyMs,
|
|
5846
|
-
[LATENCY_REALTIME_ATTR]: args.realtime ? 1 : 0
|
|
5847
|
-
};
|
|
5848
|
-
if (args.campaignId)
|
|
5849
|
-
attributes[ATTR.campaignId] = args.campaignId;
|
|
5850
|
-
trace.getTracer(TRACER_NAME2).startSpan(LATENCY_SPAN_NAME, { attributes }).end();
|
|
6611
|
+
|
|
6612
|
+
// src/providers/registry.ts
|
|
6613
|
+
function entry(id, bits) {
|
|
6614
|
+
const identity = providerIdentity(id);
|
|
6615
|
+
if (!identity) throw new Error(`PROVIDER_REGISTRY: no provider identity in contracts for '${id}'`);
|
|
6616
|
+
return { name: identity.id, capabilities: identity.capabilities, ...bits };
|
|
5851
6617
|
}
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6618
|
+
function providerEntry(name) {
|
|
6619
|
+
if (!name) return void 0;
|
|
6620
|
+
return PROVIDER_REGISTRY[PROVIDER_ALIASES[name] ?? name];
|
|
6621
|
+
}
|
|
6622
|
+
function resolveStt(name, o) {
|
|
6623
|
+
return (providerEntry(name)?.stt ?? PROVIDER_REGISTRY["deepgram"].stt)(o);
|
|
6624
|
+
}
|
|
6625
|
+
function resolveTts(name, o) {
|
|
6626
|
+
return (providerEntry(name)?.tts ?? PROVIDER_REGISTRY["deepgram"].tts)(o);
|
|
6627
|
+
}
|
|
6628
|
+
function resolveLlm(name, o) {
|
|
6629
|
+
return (providerEntry(name)?.llm ?? PROVIDER_REGISTRY["openai"].llm)(withCurrentModel(name, o));
|
|
6630
|
+
}
|
|
6631
|
+
function resolveRealtime(name, o) {
|
|
6632
|
+
return (providerEntry(name)?.realtime ?? PROVIDER_REGISTRY["openai"].realtime)(withCurrentModel(name, o));
|
|
6633
|
+
}
|
|
6634
|
+
function withCurrentModel(name, o) {
|
|
6635
|
+
if (!name || !o.model) return o;
|
|
6636
|
+
const current = currentModelFor(name, o.model);
|
|
6637
|
+
if (!current.retired) return o;
|
|
6638
|
+
console.warn("[agent] the configured model is retired; running its replacement", {
|
|
6639
|
+
provider: name,
|
|
6640
|
+
model: current.retired,
|
|
6641
|
+
replacement: current.model
|
|
6642
|
+
});
|
|
6643
|
+
return { ...o, model: current.model };
|
|
6644
|
+
}
|
|
6645
|
+
var model, lang, voice, creds, PROVIDER_REGISTRY;
|
|
6646
|
+
var init_registry = __esm({
|
|
6647
|
+
"src/providers/registry.ts"() {
|
|
6648
|
+
init_providers2();
|
|
6649
|
+
init_src();
|
|
6650
|
+
model = (o) => o.model ? { model: o.model } : {};
|
|
6651
|
+
lang = (o) => o.language ? { language: o.language } : {};
|
|
6652
|
+
voice = (o) => o.voice ? { voice: o.voice } : {};
|
|
6653
|
+
creds = (o) => o.creds ?? {};
|
|
6654
|
+
PROVIDER_REGISTRY = {
|
|
6655
|
+
deepgram: entry("deepgram", {
|
|
6656
|
+
failureClass: "vendor-api",
|
|
6657
|
+
engines: ["livekit"],
|
|
6658
|
+
stt: (o) => deepgram.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
6659
|
+
// Deepgram folds the voice into the model id (aura-2-<name>-en): wire config
|
|
6660
|
+
// passes a voice, code config passes a model — both land as `model`.
|
|
6661
|
+
tts: (o) => {
|
|
6662
|
+
const m = o.voice ?? o.model;
|
|
6663
|
+
return deepgram.tts({ ...m ? { model: m } : {}, ...creds(o) });
|
|
6664
|
+
}
|
|
6665
|
+
}),
|
|
6666
|
+
openai: entry("openai", {
|
|
6667
|
+
failureClass: "vendor-api",
|
|
6668
|
+
engines: ["livekit", "text"],
|
|
6669
|
+
llm: (o) => openai.llm({ ...model(o), ...creds(o) }),
|
|
6670
|
+
tts: (o) => openai.tts({ ...voice(o), ...model(o), ...creds(o) }),
|
|
6671
|
+
realtime: (o) => openai.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
6672
|
+
}),
|
|
6673
|
+
anthropic: entry("anthropic", {
|
|
6674
|
+
failureClass: "vendor-api",
|
|
6675
|
+
engines: ["livekit", "text"],
|
|
6676
|
+
llm: (o) => anthropic.llm({ ...model(o), ...creds(o) })
|
|
6677
|
+
}),
|
|
6678
|
+
google: entry("google", {
|
|
6679
|
+
failureClass: "vendor-api",
|
|
6680
|
+
engines: ["livekit", "text"],
|
|
6681
|
+
llm: (o) => google.llm({ ...model(o), ...creds(o) }),
|
|
6682
|
+
realtime: (o) => google.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
6683
|
+
}),
|
|
6684
|
+
cartesia: entry("cartesia", {
|
|
6685
|
+
failureClass: "vendor-api",
|
|
6686
|
+
engines: ["livekit"],
|
|
6687
|
+
stt: (o) => cartesia.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
6688
|
+
tts: (o) => cartesia.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
6689
|
+
}),
|
|
6690
|
+
elevenlabs: entry("elevenlabs", {
|
|
6691
|
+
failureClass: "vendor-api",
|
|
6692
|
+
engines: ["livekit"],
|
|
6693
|
+
tts: (o) => elevenlabs.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
6694
|
+
}),
|
|
6695
|
+
assemblyai: entry("assemblyai", {
|
|
6696
|
+
failureClass: "vendor-api",
|
|
6697
|
+
engines: ["livekit"],
|
|
6698
|
+
stt: (o) => assemblyai.stt({ ...lang(o), ...creds(o) })
|
|
6699
|
+
// no model knob
|
|
6700
|
+
}),
|
|
6701
|
+
silero: entry("silero", {
|
|
6702
|
+
failureClass: "local",
|
|
6703
|
+
engines: ["livekit"],
|
|
6704
|
+
vad: () => silero.vad()
|
|
6705
|
+
})
|
|
6706
|
+
};
|
|
5861
6707
|
}
|
|
5862
6708
|
});
|
|
5863
6709
|
|
|
5864
|
-
//
|
|
5865
|
-
var
|
|
5866
|
-
"
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
init_trace_propagation();
|
|
5870
|
-
init_attributes();
|
|
5871
|
-
init_metrics();
|
|
5872
|
-
init_latency_span();
|
|
6710
|
+
// src/providers/llm.ts
|
|
6711
|
+
var init_llm = __esm({
|
|
6712
|
+
"src/providers/llm.ts"() {
|
|
6713
|
+
init_openai_default();
|
|
6714
|
+
init_registry();
|
|
5873
6715
|
}
|
|
5874
6716
|
});
|
|
5875
6717
|
|
|
@@ -6126,7 +6968,9 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
6126
6968
|
if (result === void 0) return false;
|
|
6127
6969
|
if (!path || path === "ok") {
|
|
6128
6970
|
const ok = result.ok === true || result.kind === "ok";
|
|
6129
|
-
|
|
6971
|
+
const want = (rawVal ?? "").trim().toLowerCase();
|
|
6972
|
+
const eq2 = want === "" ? ok : String(ok) === want;
|
|
6973
|
+
return op === "!=" ? !eq2 : eq2;
|
|
6130
6974
|
}
|
|
6131
6975
|
const actual = resolvePath(result, path);
|
|
6132
6976
|
if (!op) return actual !== void 0 && actual !== null && actual !== "";
|
|
@@ -6321,28 +7165,12 @@ var init_conditions = __esm({
|
|
|
6321
7165
|
});
|
|
6322
7166
|
|
|
6323
7167
|
// src/runtime/graph/turn-text.ts
|
|
6324
|
-
function lastSentences(line) {
|
|
6325
|
-
const text = line.trim();
|
|
6326
|
-
if (!text) return [];
|
|
6327
|
-
const sentences = [];
|
|
6328
|
-
let last = 0;
|
|
6329
|
-
for (const m of text.matchAll(SENTENCE_END)) {
|
|
6330
|
-
const end = (m.index ?? 0) + m[0].length;
|
|
6331
|
-
sentences.push(text.slice(last, end));
|
|
6332
|
-
last = end;
|
|
6333
|
-
}
|
|
6334
|
-
if (last < text.length) sentences.push(text.slice(last));
|
|
6335
|
-
return sentences.slice(-2);
|
|
6336
|
-
}
|
|
6337
|
-
function isQuestion(line) {
|
|
6338
|
-
return lastSentences(line).some((s) => QUESTION_MARK.test(s));
|
|
6339
|
-
}
|
|
6340
7168
|
function wordsOf(field) {
|
|
6341
7169
|
return field.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
6342
7170
|
}
|
|
6343
7171
|
function asksFor(line, fields) {
|
|
6344
7172
|
if (!line) return false;
|
|
6345
|
-
const questions = lastSentences(line).filter((s) =>
|
|
7173
|
+
const questions = lastSentences(line).filter((s) => QUESTION_MARK2.test(s)).map((s) => s.toLowerCase().replace(/^[\s"'“(]*(?:and|so|ok|okay|great|thanks|sure|alright|now)[,\s]+/, "").trim());
|
|
6346
7174
|
return questions.some((q) => {
|
|
6347
7175
|
if (YES_NO.test(q) && !POLITE_REQUEST.test(q)) return false;
|
|
6348
7176
|
const words = new Set(q.match(/[a-z0-9]+/g) ?? []);
|
|
@@ -6352,11 +7180,11 @@ function asksFor(line, fields) {
|
|
|
6352
7180
|
});
|
|
6353
7181
|
});
|
|
6354
7182
|
}
|
|
6355
|
-
var
|
|
7183
|
+
var QUESTION_MARK2, POLITE_REQUEST, YES_NO;
|
|
6356
7184
|
var init_turn_text = __esm({
|
|
6357
7185
|
"src/runtime/graph/turn-text.ts"() {
|
|
6358
|
-
|
|
6359
|
-
|
|
7186
|
+
init_src();
|
|
7187
|
+
QUESTION_MARK2 = /[??؟]/;
|
|
6360
7188
|
POLITE_REQUEST = /^(?:(?:could|can|would|will) you (?:please )?(?:tell|give|share|say|spell|repeat|read|provide|confirm|let me know)|(?:could|can|may) (?:i|we) (?:please )?(?:get|have|take|grab))\b/;
|
|
6361
7189
|
YES_NO = /^(?:would|do|does|did|is|are|was|were|can|could|shall|should|will|have|has|had|may|might|am)\b/;
|
|
6362
7190
|
}
|
|
@@ -6939,7 +7767,8 @@ async function nextTurn(deps, state, reprompt, opts) {
|
|
|
6939
7767
|
...opts?.deferCapture ? { deferCapture: true } : {},
|
|
6940
7768
|
...opts?.number ? { number: opts.number } : {},
|
|
6941
7769
|
...opts?.fields?.length ? { fields: opts.fields } : {},
|
|
6942
|
-
...opts?.answering && opts.answering.fields.length > 0 ? { answering: opts.answering } : {}
|
|
7770
|
+
...opts?.answering && opts.answering.fields.length > 0 ? { answering: opts.answering } : {},
|
|
7771
|
+
...opts?.captureAfterRead ? { captureAfterRead: true } : {}
|
|
6943
7772
|
});
|
|
6944
7773
|
if (evt.kind === "hangup") return { result: { kind: "end" } };
|
|
6945
7774
|
if (evt.kind === "control") return { result: evt.result };
|
|
@@ -6966,7 +7795,7 @@ async function nextTurn(deps, state, reprompt, opts) {
|
|
|
6966
7795
|
state.lastNotInContext = evt.notInContext;
|
|
6967
7796
|
state.lastAgentText = evt.agentText;
|
|
6968
7797
|
state.lastDtmf = void 0;
|
|
6969
|
-
return { text: evt.text };
|
|
7798
|
+
return { text: evt.text, ...evt.capture ? { capture: evt.capture } : {} };
|
|
6970
7799
|
}
|
|
6971
7800
|
}
|
|
6972
7801
|
function detectYesNo(text) {
|
|
@@ -6976,6 +7805,58 @@ function detectYesNo(text) {
|
|
|
6976
7805
|
if (no && !yes) return "no";
|
|
6977
7806
|
return null;
|
|
6978
7807
|
}
|
|
7808
|
+
function renderLine(template, state, deps) {
|
|
7809
|
+
return { spoken: interpolate(template, scopeFor(state, deps)), model: interpolate(template, modelScopeFor(state, deps)) };
|
|
7810
|
+
}
|
|
7811
|
+
function askLinesFor(node, state, deps, askText, label, rephrase) {
|
|
7812
|
+
const render = (line) => renderLine(line, state, deps);
|
|
7813
|
+
const ctx = deps.askContext;
|
|
7814
|
+
const english = isEnglishLanguage(ctx?.language);
|
|
7815
|
+
const generated = readAskGeneratedLines(node.config.generated);
|
|
7816
|
+
const current = generated !== null && ctx !== void 0 && generated.basis === askLinesBasis({
|
|
7817
|
+
persona: ctx.persona,
|
|
7818
|
+
routing: ctx.routing ?? null,
|
|
7819
|
+
language: ctx.language ?? null,
|
|
7820
|
+
ask: str(node.config.ask),
|
|
7821
|
+
instructions: str(node.config.instructions)
|
|
7822
|
+
});
|
|
7823
|
+
if (generated && !current) deps.events.note("ask.reask_lines_stale", { node: node.id });
|
|
7824
|
+
const usable = current && rephrase !== "never" ? generated : null;
|
|
7825
|
+
const author = authorReasksOf(node.config.reasks).map(render);
|
|
7826
|
+
const pool = author.length ? author : usable && usable.reasks.length ? usable.reasks.map(render) : english ? templateReasks(askText.spoken).map((spoken, i) => ({ spoken, model: templateReasks(askText.model)[i] })) : rephrase === "never" ? [askText] : [];
|
|
7827
|
+
const offerTemplate = english ? skipOfferTemplate(label) : void 0;
|
|
7828
|
+
const skipOffer = usable?.skipOffer ? render(usable.skipOffer) : offerTemplate ? { spoken: offerTemplate, model: offerTemplate } : void 0;
|
|
7829
|
+
const voicedAsk = rephrase === "all" && usable?.voicedAsk ? render(usable.voicedAsk) : void 0;
|
|
7830
|
+
return { pool, ...skipOffer ? { skipOffer } : {}, ...voicedAsk ? { voicedAsk } : {} };
|
|
7831
|
+
}
|
|
7832
|
+
function skipOfferVerdict(text, dtmf) {
|
|
7833
|
+
if (dtmf === "1") return "yes";
|
|
7834
|
+
if (dtmf === "2") return "no";
|
|
7835
|
+
if (!text.trim()) return null;
|
|
7836
|
+
if (KEEP_ASKING.test(text)) return "no";
|
|
7837
|
+
if (SKIP_INTENT.test(text)) return OFFERS_IT.test(text) ? null : "yes";
|
|
7838
|
+
return detectYesNo(text);
|
|
7839
|
+
}
|
|
7840
|
+
async function readYesNo(t, state, deps) {
|
|
7841
|
+
const words = skipOfferVerdict(t.text, t.dtmf);
|
|
7842
|
+
if (words) return words;
|
|
7843
|
+
if (!t.text.trim()) return null;
|
|
7844
|
+
return await resolveBranch(
|
|
7845
|
+
[
|
|
7846
|
+
{ id: "yes", label: "The caller agrees to skip it for now." },
|
|
7847
|
+
{ id: "no", label: "The caller does not want to skip it \u2014 they will give it." }
|
|
7848
|
+
],
|
|
7849
|
+
evalCtxOf(state, deps)
|
|
7850
|
+
);
|
|
7851
|
+
}
|
|
7852
|
+
function briefingScope(template, state, deps) {
|
|
7853
|
+
const scope2 = scopeFor(state, deps);
|
|
7854
|
+
const data = deps.processRt.getData();
|
|
7855
|
+
const notProvided = templateFieldRefs(template).filter((f) => isRequiredField(deps, f) && !hasSlot(data, f));
|
|
7856
|
+
if (notProvided.length === 0) return scope2;
|
|
7857
|
+
const filled = Object.fromEntries(notProvided.map((f) => [f, "not provided"]));
|
|
7858
|
+
return { ...scope2, ...filled, slots: { ...scope2.slots, ...filled } };
|
|
7859
|
+
}
|
|
6979
7860
|
function formatCollected(deps) {
|
|
6980
7861
|
return Object.entries(deps.processRt.getData()).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(forModel(deps, k, v))}`).join("\n");
|
|
6981
7862
|
}
|
|
@@ -7029,6 +7910,27 @@ function positiveMs(raw, fallback) {
|
|
|
7029
7910
|
const n = Number(raw);
|
|
7030
7911
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
7031
7912
|
}
|
|
7913
|
+
function isRequiredField(deps, name) {
|
|
7914
|
+
return deps.model.requiredFields.has(name.trim());
|
|
7915
|
+
}
|
|
7916
|
+
function requiredInputsOf(node, state, deps) {
|
|
7917
|
+
const baked = node.config.requiredInputs;
|
|
7918
|
+
if (Array.isArray(baked)) return baked.filter((x) => typeof x === "string");
|
|
7919
|
+
const cached2 = state.derivedInputs?.get(node.id);
|
|
7920
|
+
if (cached2) return cached2;
|
|
7921
|
+
const derived = requiredInputsFor(node.type, node.config, (f) => isRequiredField(deps, f));
|
|
7922
|
+
(state.derivedInputs ??= /* @__PURE__ */ new Map()).set(node.id, derived);
|
|
7923
|
+
return derived;
|
|
7924
|
+
}
|
|
7925
|
+
function blockOnMissingInputs(node, state, deps, inputs, tool) {
|
|
7926
|
+
const data = deps.processRt.getData();
|
|
7927
|
+
const missing = inputs.filter((f) => !hasSlot(data, f)).map((f) => f.trim());
|
|
7928
|
+
if (missing.length === 0) return null;
|
|
7929
|
+
deps.events.note("step.missing_inputs", { node: node.id, missing });
|
|
7930
|
+
state.nodeResults[node.id] = { ok: false, error: "missing_required", missing };
|
|
7931
|
+
if (tool) state.toolResults[node.id] = { ok: false, status: 0, data: { kind: "blocked", reason: "missing_required", missing } };
|
|
7932
|
+
return deps.model.selectByPort(node.id, MISSING_PORT_ID) !== null ? { kind: "take", port: MISSING_PORT_ID } : routeByPort(node, deps, "fail");
|
|
7933
|
+
}
|
|
7032
7934
|
function routeByPort(node, deps, port) {
|
|
7033
7935
|
return deps.model.selectByPort(node.id, port) !== null ? { kind: "take", port } : { kind: "advance" };
|
|
7034
7936
|
}
|
|
@@ -7040,11 +7942,15 @@ function menuDigitOf(b) {
|
|
|
7040
7942
|
function menuLabelOf(label) {
|
|
7041
7943
|
return label.replace(/^\s*[0-9*#]\s*[-—–:.]*\s*/, "").trim() || label.trim();
|
|
7042
7944
|
}
|
|
7043
|
-
|
|
7945
|
+
async function speakLine(text, deps) {
|
|
7946
|
+
await speak(text, deps);
|
|
7947
|
+
}
|
|
7948
|
+
var ASK_MAX_CLARIFY, COLLECT_MAX_STALLED_TURNS, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, legacyAskExecutor, ASK_POLICIES, askExecutor, KEEP_ASKING, SKIP_INTENT, OFFERS_IT, gateExecutor, endExecutor, handoffExecutor, OPENING_SILENCE_MS, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, EmailStepTimeout, EMAIL_SEND_TIMEOUT_MS, emailExecutor, passthroughExecutor, EXECUTORS;
|
|
7044
7949
|
var init_executors = __esm({
|
|
7045
7950
|
"src/runtime/graph/executors.ts"() {
|
|
7046
7951
|
init_log();
|
|
7047
7952
|
init_src();
|
|
7953
|
+
init_reask_templates();
|
|
7048
7954
|
init_interpolate();
|
|
7049
7955
|
init_builtins();
|
|
7050
7956
|
init_conditions();
|
|
@@ -7066,9 +7972,11 @@ var init_executors = __esm({
|
|
|
7066
7972
|
NO_WORDS = /\b(no|nope|nah|wrong|incorrect|negative|not right|that'?s not|change|actually)\b/i;
|
|
7067
7973
|
confirmExecutor = async (node, state, deps) => {
|
|
7068
7974
|
const text = interpolate(str(node.config.text), scopeFor(state, deps));
|
|
7069
|
-
|
|
7975
|
+
const line = text || "Is that correct?";
|
|
7976
|
+
await speak(line, deps);
|
|
7977
|
+
const reprompt = isEnglishLanguage(deps.askContext?.language) ? notCaught(line) : line;
|
|
7070
7978
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7071
|
-
const t = await nextTurn(deps, state,
|
|
7979
|
+
const t = await nextTurn(deps, state, reprompt);
|
|
7072
7980
|
if ("result" in t) return t.result;
|
|
7073
7981
|
let verdict = null;
|
|
7074
7982
|
if (t.dtmf === "1") verdict = "yes";
|
|
@@ -7096,7 +8004,7 @@ var init_executors = __esm({
|
|
|
7096
8004
|
deps.events.note("confirm.unresolved", { node: node.id });
|
|
7097
8005
|
return { kind: "advance" };
|
|
7098
8006
|
};
|
|
7099
|
-
|
|
8007
|
+
legacyAskExecutor = async (node, state, deps) => {
|
|
7100
8008
|
const slot = str(node.config.slot) || node.id;
|
|
7101
8009
|
const authoredSlot = slot === node.id ? "" : slot.trim();
|
|
7102
8010
|
const label = authoredSlot.replace(/[_-]+/g, " ");
|
|
@@ -7137,6 +8045,165 @@ var init_executors = __esm({
|
|
|
7137
8045
|
deps.events.note("ask.unfilled", { slot: slot.trim() });
|
|
7138
8046
|
return { kind: "advance" };
|
|
7139
8047
|
};
|
|
8048
|
+
ASK_POLICIES = /* @__PURE__ */ new Set(["offer_skip", "fallback", "end"]);
|
|
8049
|
+
askExecutor = async (node, state, deps) => {
|
|
8050
|
+
const cfg = node.config;
|
|
8051
|
+
if (typeof cfg.maxAttempts !== "number") return legacyAskExecutor(node, state, deps);
|
|
8052
|
+
const slot = str(cfg.slot) || node.id;
|
|
8053
|
+
const field = slot.trim();
|
|
8054
|
+
const label = (slot === node.id ? "" : field).replace(/[_-]+/g, " ");
|
|
8055
|
+
const required = cfg.required !== false;
|
|
8056
|
+
const maxAttempts = Math.min(6, Math.max(1, Math.round(cfg.maxAttempts)));
|
|
8057
|
+
const policy = ASK_POLICIES.has(str(cfg.onExhausted)) ? str(cfg.onExhausted) : "offer_skip";
|
|
8058
|
+
const rephrase = cfg.rephrase === "all" || cfg.rephrase === "never" ? cfg.rephrase : "reasks";
|
|
8059
|
+
const unfilledWired = deps.model.selectByPort(node.id, UNFILLED_PORT_ID) !== null;
|
|
8060
|
+
const captured = () => hasSlot(deps.processRt.getData(), slot);
|
|
8061
|
+
const exitNext = { kind: "advance" };
|
|
8062
|
+
if (captured()) {
|
|
8063
|
+
deps.events.note("ask.skipped", { slot: field, reason: "already known" });
|
|
8064
|
+
return exitNext;
|
|
8065
|
+
}
|
|
8066
|
+
const askText = renderLine(str(cfg.ask) || defaultAskText(slot, node.id), state, deps);
|
|
8067
|
+
const lines = askLinesFor(node, state, deps, askText, label, rephrase);
|
|
8068
|
+
const closing = () => interpolate(str(cfg.exhaustedMessage) || DEFAULT_ASK_EXHAUSTED_MESSAGE, scopeFor(state, deps));
|
|
8069
|
+
const giveUp = async () => {
|
|
8070
|
+
state.closingLine = closing();
|
|
8071
|
+
if (unfilledWired) return { kind: "take", port: UNFILLED_PORT_ID };
|
|
8072
|
+
await speak(state.closingLine, deps);
|
|
8073
|
+
return { kind: "end" };
|
|
8074
|
+
};
|
|
8075
|
+
let lastLine = askText;
|
|
8076
|
+
let rotation = 0;
|
|
8077
|
+
const nextLine = () => {
|
|
8078
|
+
const pool = lines.pool;
|
|
8079
|
+
if (pool.length === 0) return askText;
|
|
8080
|
+
let line = pool[rotation++ % pool.length];
|
|
8081
|
+
if (line.spoken === lastLine.spoken && pool.length > 1) line = pool[rotation++ % pool.length];
|
|
8082
|
+
return line;
|
|
8083
|
+
};
|
|
8084
|
+
const sayLine = async (line) => {
|
|
8085
|
+
lastLine = line;
|
|
8086
|
+
await speak(line.spoken, deps);
|
|
8087
|
+
};
|
|
8088
|
+
const kind = cfg.type === "phone" || cfg.type === "number" ? cfg.type : void 0;
|
|
8089
|
+
const turnOpts = (since) => ({
|
|
8090
|
+
fields: [slot],
|
|
8091
|
+
// the ask's question is about its slot by definition: every turn here answers it (a free-text slot takes the turn)
|
|
8092
|
+
answering: { fields: answerable(deps, [slot]), step: node.id, since },
|
|
8093
|
+
...kind ? { number: { field: slot, kind } } : {}
|
|
8094
|
+
});
|
|
8095
|
+
let askedAt = Date.now();
|
|
8096
|
+
if (required && state.skipped?.[field]) {
|
|
8097
|
+
const revisits = (state.skipRevisits?.[field] ?? 0) + 1;
|
|
8098
|
+
state.skipRevisits = { ...state.skipRevisits, [field]: revisits };
|
|
8099
|
+
if (revisits >= 2) {
|
|
8100
|
+
deps.events.note("ask.revisit_exhausted", { slot: field });
|
|
8101
|
+
return giveUp();
|
|
8102
|
+
}
|
|
8103
|
+
deps.events.note("ask.revisit", { slot: field });
|
|
8104
|
+
await sayLine(askText);
|
|
8105
|
+
const t = await nextTurn(deps, state, () => sayLine(nextLine()), turnOpts(askedAt));
|
|
8106
|
+
if ("result" in t) return t.result;
|
|
8107
|
+
if (captured()) {
|
|
8108
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8109
|
+
return exitNext;
|
|
8110
|
+
}
|
|
8111
|
+
deps.events.note("ask.revisit_unfilled", { slot: field });
|
|
8112
|
+
return giveUp();
|
|
8113
|
+
}
|
|
8114
|
+
const clarify = async (question) => {
|
|
8115
|
+
const endOn = lines.pool.length > 0 ? nextLine() : null;
|
|
8116
|
+
const what = label || "the detail just asked about";
|
|
8117
|
+
const parts = [
|
|
8118
|
+
`You are collecting "${what}" from the caller. They haven't given a clear, valid value yet.`,
|
|
8119
|
+
question ? "The caller asked a question: answer it briefly and honestly first." : "Acknowledge what they said in a few words.",
|
|
8120
|
+
// the model-safe copies only: caller-given values fenced and tokenised, never raw (§B.9)
|
|
8121
|
+
endOn ? `Then ask for just that again, ending on this question: "${endOn.model}"` : "Then briefly ask again for just that.",
|
|
8122
|
+
`Don't repeat this line word for word: "${lastLine.model}"`
|
|
8123
|
+
];
|
|
8124
|
+
const instructions = interpolate(str(cfg.instructions), modelScopeFor(state, deps));
|
|
8125
|
+
if (instructions) parts.push(`About this question (from the flow's author): ${instructions}`);
|
|
8126
|
+
if (endOn) lastLine = endOn;
|
|
8127
|
+
await withHistory(deps, historyPolicyOf(node), () => genReply(deps, parts.join("\n")));
|
|
8128
|
+
};
|
|
8129
|
+
const round = async (attempts) => {
|
|
8130
|
+
let tries = 0;
|
|
8131
|
+
for (let turn = 1; ; turn++) {
|
|
8132
|
+
const t = await nextTurn(deps, state, () => sayLine(nextLine()), turnOpts(askedAt));
|
|
8133
|
+
if ("result" in t) return t.result;
|
|
8134
|
+
if (captured()) return "captured";
|
|
8135
|
+
const text = t.text.trim();
|
|
8136
|
+
const filler = isFiller(text);
|
|
8137
|
+
const question = !filler && isQuestion(text);
|
|
8138
|
+
if (!question) tries += 1;
|
|
8139
|
+
if (tries >= attempts) return "exhausted";
|
|
8140
|
+
if (turn >= 2 * attempts) {
|
|
8141
|
+
deps.events.note("ask.turn_cap", { slot: field, turns: turn });
|
|
8142
|
+
return "exhausted";
|
|
8143
|
+
}
|
|
8144
|
+
deps.events.note("ask.clarify", { slot: field, turn, kind: filler ? "filler" : question ? "question" : "answer" });
|
|
8145
|
+
if (filler && lines.pool.length > 0 || rephrase === "never") await sayLine(nextLine());
|
|
8146
|
+
else await clarify(question);
|
|
8147
|
+
}
|
|
8148
|
+
};
|
|
8149
|
+
await sayLine(lines.voicedAsk ?? askText);
|
|
8150
|
+
let outcome = await round(maxAttempts);
|
|
8151
|
+
if (outcome === "captured") {
|
|
8152
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8153
|
+
return exitNext;
|
|
8154
|
+
}
|
|
8155
|
+
if (outcome !== "exhausted") return outcome;
|
|
8156
|
+
if (!required) {
|
|
8157
|
+
deps.events.note("ask.unfilled", { slot: field });
|
|
8158
|
+
return exitNext;
|
|
8159
|
+
}
|
|
8160
|
+
deps.events.note("ask.exhausted", { slot: field, policy });
|
|
8161
|
+
if (policy === "end" || policy === "fallback") return giveUp();
|
|
8162
|
+
const offer = lines.skipOffer;
|
|
8163
|
+
if (!offer) return giveUp();
|
|
8164
|
+
state.closingLine = closing();
|
|
8165
|
+
const skip = (reason) => {
|
|
8166
|
+
state.skipped = { ...state.skipped, [field]: reason };
|
|
8167
|
+
deps.events.note("ask.skipped_by_caller", { slot: field, reason });
|
|
8168
|
+
return unfilledWired ? { kind: "take", port: UNFILLED_PORT_ID } : exitNext;
|
|
8169
|
+
};
|
|
8170
|
+
let unclear = 0;
|
|
8171
|
+
await sayLine(offer);
|
|
8172
|
+
for (; ; ) {
|
|
8173
|
+
const t = await nextTurn(deps, state, offer.spoken, { fields: [slot], captureAfterRead: true });
|
|
8174
|
+
if ("result" in t) return t.result;
|
|
8175
|
+
const verdict = await readYesNo(t, state, deps);
|
|
8176
|
+
if (verdict === "yes") {
|
|
8177
|
+
await t.capture?.({ exclude: [slot] });
|
|
8178
|
+
return skip("caller");
|
|
8179
|
+
}
|
|
8180
|
+
if (verdict === "no") {
|
|
8181
|
+
await t.capture?.({ exclude: [slot] });
|
|
8182
|
+
deps.events.note("ask.skip_declined", { slot: field });
|
|
8183
|
+
askedAt = Date.now();
|
|
8184
|
+
await sayLine(nextLine());
|
|
8185
|
+
outcome = await round(maxAttempts);
|
|
8186
|
+
if (outcome === "captured") {
|
|
8187
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8188
|
+
return exitNext;
|
|
8189
|
+
}
|
|
8190
|
+
if (outcome !== "exhausted") return outcome;
|
|
8191
|
+
deps.events.note("ask.exhausted", { slot: field, policy, round: 2 });
|
|
8192
|
+
return giveUp();
|
|
8193
|
+
}
|
|
8194
|
+
await t.capture?.();
|
|
8195
|
+
if (captured()) {
|
|
8196
|
+
deps.events.note("ask.captured", { slot: field });
|
|
8197
|
+
return exitNext;
|
|
8198
|
+
}
|
|
8199
|
+
unclear += 1;
|
|
8200
|
+
if (unclear >= 2) return skip("unclear");
|
|
8201
|
+
await sayLine(offer);
|
|
8202
|
+
}
|
|
8203
|
+
};
|
|
8204
|
+
KEEP_ASKING = /\b(?:don'?t|do not|not|never|no need to)(?:\s+\w+){0,3}\s+skip\b|\bi(?:'ve| have)(?: got)? it\b/i;
|
|
8205
|
+
SKIP_INTENT = /\b(?:skip|don'?t have|do not have|haven'?t got|move on|pass on (?:it|that|this))\b/i;
|
|
8206
|
+
OFFERS_IT = /\b(?:give|here|hold on|let me|it'?s|my number)\b/i;
|
|
7140
8207
|
gateExecutor = async (node, _state, deps) => {
|
|
7141
8208
|
const required = Array.isArray(node.config.requiredFields) ? node.config.requiredFields.filter((x) => typeof x === "string") : [];
|
|
7142
8209
|
const data = deps.processRt.getData();
|
|
@@ -7159,7 +8226,7 @@ var init_executors = __esm({
|
|
|
7159
8226
|
}
|
|
7160
8227
|
const reason = str(cfg.reason) || `flow:${node.id}`;
|
|
7161
8228
|
const mode = cfg.mode === "warm" || cfg.mode === "cold" ? cfg.mode : void 0;
|
|
7162
|
-
const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing),
|
|
8229
|
+
const briefing = str(cfg.briefing) ? interpolate(str(cfg.briefing), briefingScope(str(cfg.briefing), state, deps)) : void 0;
|
|
7163
8230
|
deps.events.note("handoff.start", { node: node.id, to: target, ...mode ? { mode } : {} });
|
|
7164
8231
|
try {
|
|
7165
8232
|
await deps.ctx.handoff(target, {
|
|
@@ -7263,9 +8330,17 @@ ${collected}`);
|
|
|
7263
8330
|
const toolRef = str(cfg.toolRef);
|
|
7264
8331
|
const registry = deps.resolvers?.registryTools;
|
|
7265
8332
|
if (toolRef && registry) {
|
|
7266
|
-
deps.events.note("tool.invoked", { node: node.id, name: toolRef, ref: true });
|
|
7267
8333
|
try {
|
|
7268
8334
|
const spec = (await registry.catalog()).find((t) => t.name === toolRef);
|
|
8335
|
+
const blocked2 = blockOnMissingInputs(
|
|
8336
|
+
node,
|
|
8337
|
+
state,
|
|
8338
|
+
deps,
|
|
8339
|
+
Object.keys(spec?.input ?? {}).filter((f) => isRequiredField(deps, f)),
|
|
8340
|
+
true
|
|
8341
|
+
);
|
|
8342
|
+
if (blocked2) return blocked2;
|
|
8343
|
+
deps.events.note("tool.invoked", { node: node.id, name: toolRef, ref: true });
|
|
7269
8344
|
const slots2 = deps.processRt.getData();
|
|
7270
8345
|
const bag2 = {};
|
|
7271
8346
|
for (const field of Object.keys(spec?.input ?? {})) {
|
|
@@ -7298,6 +8373,8 @@ ${collected}`);
|
|
|
7298
8373
|
deps.events.note("tool.unavailable", { node: node.id, name: toolRef });
|
|
7299
8374
|
return routeByOutcome();
|
|
7300
8375
|
}
|
|
8376
|
+
const blocked = blockOnMissingInputs(node, state, deps, requiredInputsOf(node, state, deps), true);
|
|
8377
|
+
if (blocked) return blocked;
|
|
7301
8378
|
const url = str(cfg.url);
|
|
7302
8379
|
if (!url) {
|
|
7303
8380
|
state.toolResults[node.id] = { ok: false, status: 0, data: null };
|
|
@@ -7523,7 +8600,7 @@ ${collected}`);
|
|
|
7523
8600
|
deps.events.note("menu.selected", { node: node.id, choice });
|
|
7524
8601
|
return { kind: "take", port: choice };
|
|
7525
8602
|
}
|
|
7526
|
-
await speak(prompt, deps);
|
|
8603
|
+
await speak(isEnglishLanguage(deps.askContext?.language) && prompt ? notCaught(prompt) : prompt, deps);
|
|
7527
8604
|
}
|
|
7528
8605
|
}
|
|
7529
8606
|
deps.events.note("menu.unmatched", { node: node.id });
|
|
@@ -7796,7 +8873,7 @@ ${collected}`);
|
|
|
7796
8873
|
}
|
|
7797
8874
|
await speak(
|
|
7798
8875
|
interpolate(
|
|
7799
|
-
str(cfg.incompleteMessage) ||
|
|
8876
|
+
str(cfg.incompleteMessage) || DEFAULT_ASK_EXHAUSTED_MESSAGE,
|
|
7800
8877
|
scopeFor(state, deps)
|
|
7801
8878
|
),
|
|
7802
8879
|
deps
|
|
@@ -7852,6 +8929,7 @@ ${collected}`);
|
|
|
7852
8929
|
...deps.resolvers ? { resolvers: deps.resolvers } : {},
|
|
7853
8930
|
...deps.live ? { live: deps.live } : {},
|
|
7854
8931
|
...deps.security ? { security: deps.security } : {},
|
|
8932
|
+
...deps.askContext ? { askContext: deps.askContext } : {},
|
|
7855
8933
|
depth: depth + 1
|
|
7856
8934
|
});
|
|
7857
8935
|
deps.events.note("subflow.exit", { node: node.id, ref, outcome: outcome.kind });
|
|
@@ -7864,6 +8942,8 @@ ${collected}`);
|
|
|
7864
8942
|
EMAIL_SEND_TIMEOUT_MS = 1e4;
|
|
7865
8943
|
emailExecutor = async (node, state, deps) => {
|
|
7866
8944
|
const cfg = node.config;
|
|
8945
|
+
const blocked = blockOnMissingInputs(node, state, deps, requiredInputsOf(node, state, deps), false);
|
|
8946
|
+
if (blocked) return blocked;
|
|
7867
8947
|
const scope2 = scopeFor(state, deps);
|
|
7868
8948
|
const render = (key) => interpolate(str(cfg[key]), scope2).trim();
|
|
7869
8949
|
const to = render("to");
|
|
@@ -7973,6 +9053,12 @@ async function selectNext(model2, from, result, state, deps) {
|
|
|
7973
9053
|
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
|
|
7974
9054
|
});
|
|
7975
9055
|
}
|
|
9056
|
+
async function closeAtLoopGuard(state, deps) {
|
|
9057
|
+
try {
|
|
9058
|
+
await speakLine(state.closingLine ?? DEFAULT_ASK_EXHAUSTED_MESSAGE, { ctx: deps.ctx, ...deps.realtime ? { realtime: true } : {} });
|
|
9059
|
+
} catch {
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
7976
9062
|
async function runFlowGraph(program, deps) {
|
|
7977
9063
|
const model2 = buildGraphModel(program);
|
|
7978
9064
|
const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
|
|
@@ -7985,6 +9071,7 @@ async function runFlowGraph(program, deps) {
|
|
|
7985
9071
|
while (cursor !== null) {
|
|
7986
9072
|
if (++steps > maxSteps) {
|
|
7987
9073
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
|
|
9074
|
+
await closeAtLoopGuard(state, deps);
|
|
7988
9075
|
break;
|
|
7989
9076
|
}
|
|
7990
9077
|
const node = model2.node(cursor);
|
|
@@ -7997,6 +9084,7 @@ async function runFlowGraph(program, deps) {
|
|
|
7997
9084
|
const visitCap = node.config.hub === true ? HUB_MAX_NODE_VISITS : maxNodeVisits;
|
|
7998
9085
|
if (visit > visitCap) {
|
|
7999
9086
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_node_visits", nodeId: cursor });
|
|
9087
|
+
await closeAtLoopGuard(state, deps);
|
|
8000
9088
|
break;
|
|
8001
9089
|
}
|
|
8002
9090
|
state.cursor = cursor;
|
|
@@ -8017,7 +9105,8 @@ async function runFlowGraph(program, deps) {
|
|
|
8017
9105
|
...deps.depth !== void 0 ? { depth: deps.depth } : {},
|
|
8018
9106
|
...deps.busy ? { busy: deps.busy } : {},
|
|
8019
9107
|
...deps.live ? { live: deps.live } : {},
|
|
8020
|
-
...deps.security ? { security: deps.security } : {}
|
|
9108
|
+
...deps.security ? { security: deps.security } : {},
|
|
9109
|
+
...deps.askContext ? { askContext: deps.askContext } : {}
|
|
8021
9110
|
});
|
|
8022
9111
|
} catch (err) {
|
|
8023
9112
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -8050,6 +9139,7 @@ var init_interpreter = __esm({
|
|
|
8050
9139
|
init_log();
|
|
8051
9140
|
init_graph_model();
|
|
8052
9141
|
init_executors();
|
|
9142
|
+
init_src();
|
|
8053
9143
|
init_conditions();
|
|
8054
9144
|
DEFAULT_MAX_NODE_VISITS = 25;
|
|
8055
9145
|
DEFAULT_MAX_STEPS = 200;
|
|
@@ -9010,6 +10100,13 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
9010
10100
|
...number ? { numberField: number.field } : {},
|
|
9011
10101
|
...answer ? { answering: answer.field } : {}
|
|
9012
10102
|
};
|
|
10103
|
+
if (opts?.captureAfterRead) {
|
|
10104
|
+
const text = raw.text;
|
|
10105
|
+
return {
|
|
10106
|
+
...turn,
|
|
10107
|
+
capture: (o) => capture(text, false, { ...step, ...o?.exclude?.length ? { exclude: o.exclude } : {} }, answer)
|
|
10108
|
+
};
|
|
10109
|
+
}
|
|
9013
10110
|
if (opts?.deferCapture) {
|
|
9014
10111
|
const run = capture(raw.text, true, step, answer).then(() => {
|
|
9015
10112
|
if (pending === run) pending = null;
|
|
@@ -9237,14 +10334,23 @@ function defaultComplete() {
|
|
|
9237
10334
|
model: model2,
|
|
9238
10335
|
fallbackModel: RUNNER_MODEL,
|
|
9239
10336
|
helper: "agent runner",
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
10337
|
+
// With tools, a model that takes effort 'none' gets 'none' (OpenAI refuses tools at any other effort on chat
|
|
10338
|
+
// completions for GPT-5.2 and later), and a reasoning_effort 400 is learned and retried once (burn-down G-45).
|
|
10339
|
+
call: (m) => withReasoningEffortRetry(
|
|
10340
|
+
{ model: m, tools: tools.length > 0, requested: "low" },
|
|
10341
|
+
() => client.chat.completions.create(
|
|
10342
|
+
{
|
|
10343
|
+
model: m,
|
|
10344
|
+
...chatCompletionParams(m, {
|
|
10345
|
+
...temperature !== void 0 ? { temperature } : {},
|
|
10346
|
+
reasoningEffort: "low",
|
|
10347
|
+
tools: tools.length > 0
|
|
10348
|
+
}),
|
|
10349
|
+
messages,
|
|
10350
|
+
...tools.length ? { tools } : {}
|
|
10351
|
+
},
|
|
10352
|
+
{ timeout: RUNNER_TIMEOUT_MS }
|
|
10353
|
+
)
|
|
9248
10354
|
),
|
|
9249
10355
|
warn: graphWarn
|
|
9250
10356
|
});
|
|
@@ -9755,7 +10861,8 @@ var NOT_SENT = /* @__PURE__ */ new Set([
|
|
|
9755
10861
|
"email_provider_error",
|
|
9756
10862
|
"email_send_failed",
|
|
9757
10863
|
"insufficient_balance",
|
|
9758
|
-
|
|
10864
|
+
// a hard budget refused it (the key's monthly budget replaced the old per-key spend cap — monetization MON2)
|
|
10865
|
+
"budget_exceeded"
|
|
9759
10866
|
]);
|
|
9760
10867
|
function dedupeConversationEmails(resolver) {
|
|
9761
10868
|
const sends = /* @__PURE__ */ new Map();
|
|
@@ -10053,7 +11160,8 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
10053
11160
|
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
10054
11161
|
...opts.busy ? { busy: opts.busy } : {},
|
|
10055
11162
|
...opts.live ? { live: opts.live } : {},
|
|
10056
|
-
...security ? { security } : {}
|
|
11163
|
+
...security ? { security } : {},
|
|
11164
|
+
...opts.askContext ? { askContext: opts.askContext } : {}
|
|
10057
11165
|
});
|
|
10058
11166
|
graphLog("outcome", outcome);
|
|
10059
11167
|
return outcome;
|
|
@@ -10569,14 +11677,15 @@ function createProcessRuntime(def) {
|
|
|
10569
11677
|
console.error("[process] onField threw", err);
|
|
10570
11678
|
}
|
|
10571
11679
|
};
|
|
10572
|
-
const
|
|
11680
|
+
const excluded = (name) => opts?.exclude?.some((f) => f.trim() === name.trim()) === true;
|
|
11681
|
+
const answering = opts?.answering !== void 0 && !excluded(opts.answering) ? opts.answering : void 0;
|
|
10573
11682
|
const answerField = answering !== void 0 ? def.collect[answering] : void 0;
|
|
10574
11683
|
const answerWasOpen = answering !== void 0 && !callerGave.has(answering);
|
|
10575
11684
|
const status = primitive.getFieldStatus();
|
|
10576
11685
|
const captured = (name) => status.find((f) => f.name === name)?.captured === true;
|
|
10577
11686
|
const stepFields = opts?.stepFields;
|
|
10578
11687
|
const named = (name) => stepFields?.some((f) => f.trim() === name.trim()) === true;
|
|
10579
|
-
const spoken = Object.entries(def.collect).filter(([, f]) => f.fromCaller !== false);
|
|
11688
|
+
const spoken = Object.entries(def.collect).filter(([name, f]) => f.fromCaller !== false && !excluded(name));
|
|
10580
11689
|
const open = (name) => !captured(name) || named(name) && !callerGave.has(name);
|
|
10581
11690
|
const remaining = spoken.filter(([name]) => open(name) && (!completed || stepFields === void 0 || named(name)));
|
|
10582
11691
|
const correctable = spoken.filter(([name]) => captured(name) && named(name) && callerGave.has(name));
|
|
@@ -11099,13 +12208,13 @@ function createTextSessionAdapter(transport) {
|
|
|
11099
12208
|
};
|
|
11100
12209
|
function setState(next) {
|
|
11101
12210
|
const raw = String(next);
|
|
11102
|
-
const
|
|
12211
|
+
const norm2 = normalizeState(raw);
|
|
11103
12212
|
const oldState = currentState;
|
|
11104
12213
|
const rawOld = currentRaw;
|
|
11105
|
-
currentState =
|
|
12214
|
+
currentState = norm2;
|
|
11106
12215
|
currentRaw = raw;
|
|
11107
|
-
fire("state", { oldState, newState:
|
|
11108
|
-
if (NON_BUSY_STATES.has(
|
|
12216
|
+
fire("state", { oldState, newState: norm2, rawOldState: rawOld, rawNewState: raw });
|
|
12217
|
+
if (NON_BUSY_STATES.has(norm2)) releaseIdleWaiters("idle");
|
|
11109
12218
|
}
|
|
11110
12219
|
function push(text, opts) {
|
|
11111
12220
|
if (closed || text.length === 0) return;
|
|
@@ -11165,7 +12274,6 @@ function processSchemaToDefinition(schema) {
|
|
|
11165
12274
|
} : {}
|
|
11166
12275
|
};
|
|
11167
12276
|
}
|
|
11168
|
-
var DEFAULT_FLOW_PROMPT = "You are a helpful voice assistant. Keep replies short and natural.";
|
|
11169
12277
|
function toTriggerCondition(on) {
|
|
11170
12278
|
return on.startsWith("signal:") ? on : new RegExp(on, "i");
|
|
11171
12279
|
}
|
|
@@ -11185,7 +12293,7 @@ function processSchemaToAgentParts(schema, opts = {}) {
|
|
|
11185
12293
|
if (p.text.trim()) promptParts.push(p.text.trim());
|
|
11186
12294
|
}
|
|
11187
12295
|
}
|
|
11188
|
-
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") :
|
|
12296
|
+
const prompt = promptParts.length > 0 ? promptParts.join("\n\n") : DEFAULT_FLOW_PERSONA;
|
|
11189
12297
|
let triggers;
|
|
11190
12298
|
if (schema.triggers && schema.triggers.length > 0) {
|
|
11191
12299
|
triggers = {};
|
|
@@ -11362,7 +12470,8 @@ function runTextSession(opts) {
|
|
|
11362
12470
|
events,
|
|
11363
12471
|
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
|
|
11364
12472
|
...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {},
|
|
11365
|
-
...opts.resolvers ? { resolvers: opts.resolvers } : {}
|
|
12473
|
+
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
12474
|
+
...opts.askContext ? { askContext: opts.askContext } : {}
|
|
11366
12475
|
});
|
|
11367
12476
|
return {
|
|
11368
12477
|
sendUserMessage: (text) => {
|
|
@@ -12820,6 +13929,40 @@ function computeEmptyRequired(view) {
|
|
|
12820
13929
|
return empty;
|
|
12821
13930
|
}
|
|
12822
13931
|
|
|
13932
|
+
// src/flow-persona-boot.ts
|
|
13933
|
+
init_src();
|
|
13934
|
+
var VOICE_FLOW_CUE = "You are on a voice call: speak as in a natural spoken conversation.";
|
|
13935
|
+
async function resolveFlowPersona(agentId, fetchConfig, emit) {
|
|
13936
|
+
let stored = null;
|
|
13937
|
+
let unavailable = false;
|
|
13938
|
+
try {
|
|
13939
|
+
stored = await fetchConfig();
|
|
13940
|
+
} catch {
|
|
13941
|
+
unavailable = true;
|
|
13942
|
+
emit("flow.persona_unavailable", { agentId });
|
|
13943
|
+
}
|
|
13944
|
+
const persona = unavailable ? DEFAULT_FLOW_PERSONA : flowPersonaOf(stored);
|
|
13945
|
+
return {
|
|
13946
|
+
stored,
|
|
13947
|
+
unavailable,
|
|
13948
|
+
persona,
|
|
13949
|
+
basePrompt: `${persona}
|
|
13950
|
+
|
|
13951
|
+
${VOICE_FLOW_CUE}`,
|
|
13952
|
+
askContext: {
|
|
13953
|
+
persona,
|
|
13954
|
+
routing: stored?.routingInstructions ?? null,
|
|
13955
|
+
language: stored?.language ?? null
|
|
13956
|
+
}
|
|
13957
|
+
};
|
|
13958
|
+
}
|
|
13959
|
+
function recordBootNotes(notes, graph, process2) {
|
|
13960
|
+
for (const n of notes) {
|
|
13961
|
+
if (graph) graph.note(n.kind, n.data);
|
|
13962
|
+
else process2.recordEvent({ kind: n.kind, data: n.data });
|
|
13963
|
+
}
|
|
13964
|
+
}
|
|
13965
|
+
|
|
12823
13966
|
// src/agent-defaults.ts
|
|
12824
13967
|
init_providers2();
|
|
12825
13968
|
init_registry();
|
|
@@ -12897,7 +14040,7 @@ async function resolvePipeline(cfg, call, preloadedVad) {
|
|
|
12897
14040
|
() => language.startsWith("en") ? livekitTurn.english() : livekitTurn.multilingual(),
|
|
12898
14041
|
() => language.startsWith("en") ? livekitTurn.english() : livekitTurn.multilingual()
|
|
12899
14042
|
) : null;
|
|
12900
|
-
const [stt,
|
|
14043
|
+
const [stt, llm4, tts, vad] = await Promise.all([
|
|
12901
14044
|
sttFactory2(call),
|
|
12902
14045
|
llmFactory2(call),
|
|
12903
14046
|
ttsFactory2(call),
|
|
@@ -12913,7 +14056,7 @@ async function resolvePipeline(cfg, call, preloadedVad) {
|
|
|
12913
14056
|
});
|
|
12914
14057
|
}
|
|
12915
14058
|
}
|
|
12916
|
-
return { stt, llm:
|
|
14059
|
+
return { stt, llm: llm4, tts, vad, turnDetector };
|
|
12917
14060
|
}
|
|
12918
14061
|
var DEFAULT_REQUIRED_ENV = [
|
|
12919
14062
|
"LIVEKIT_URL",
|
|
@@ -16392,13 +17535,7 @@ function isSipParticipant2(p) {
|
|
|
16392
17535
|
}
|
|
16393
17536
|
|
|
16394
17537
|
// src/runtime/prompt-compose.ts
|
|
16395
|
-
|
|
16396
|
-
const routing = routingInstructions?.trim();
|
|
16397
|
-
return routing ? `${prompt}
|
|
16398
|
-
|
|
16399
|
-
# Routing
|
|
16400
|
-
${routing}` : prompt;
|
|
16401
|
-
}
|
|
17538
|
+
init_src();
|
|
16402
17539
|
function composeAgentInstructions(input) {
|
|
16403
17540
|
const composed = composeSystemPrompt(input.prompt, input.routingInstructions);
|
|
16404
17541
|
return input.graph ? composed : input.augment(composed);
|
|
@@ -16417,7 +17554,7 @@ async function answerTextTurn(deps) {
|
|
|
16417
17554
|
const { turn, config } = deps;
|
|
16418
17555
|
const agentId = turn.agentId;
|
|
16419
17556
|
const now = deps.now ?? Date.now;
|
|
16420
|
-
const { voice: voice3, llm:
|
|
17557
|
+
const { voice: voice3, llm: llm4, initializeLogger, loggerOptions } = await import('@livekit/agents');
|
|
16421
17558
|
if (loggerOptions() === void 0) initializeLogger({ pretty: false, level: "warn" });
|
|
16422
17559
|
let effective = config;
|
|
16423
17560
|
let pipeline = null;
|
|
@@ -16459,7 +17596,7 @@ async function answerTextTurn(deps) {
|
|
|
16459
17596
|
getCtx
|
|
16460
17597
|
});
|
|
16461
17598
|
const instructions = processRt.augmentPrompt(composeSystemPrompt(effective.prompt, effective.routingInstructions));
|
|
16462
|
-
const chatCtx =
|
|
17599
|
+
const chatCtx = llm4.ChatContext.empty();
|
|
16463
17600
|
let skipReply = false;
|
|
16464
17601
|
for (const h of turn.history) {
|
|
16465
17602
|
if (h.role === "user" && security && security.inputGuard.check(h.content).action === "block") {
|
|
@@ -16979,14 +18116,14 @@ function normalize(text) {
|
|
|
16979
18116
|
return text.toLowerCase().replace(/\s+/g, " ").trim();
|
|
16980
18117
|
}
|
|
16981
18118
|
function detectVoicemailGreeting(text) {
|
|
16982
|
-
const
|
|
16983
|
-
if (!
|
|
16984
|
-
return VOICEMAIL_PATTERNS.some((re) => re.test(
|
|
18119
|
+
const norm2 = normalize(text);
|
|
18120
|
+
if (!norm2) return false;
|
|
18121
|
+
return VOICEMAIL_PATTERNS.some((re) => re.test(norm2));
|
|
16985
18122
|
}
|
|
16986
18123
|
function detectBeepCue(text) {
|
|
16987
|
-
const
|
|
16988
|
-
if (!
|
|
16989
|
-
return BEEP_CUE_PATTERNS.some((re) => re.test(
|
|
18124
|
+
const norm2 = normalize(text);
|
|
18125
|
+
if (!norm2) return false;
|
|
18126
|
+
return BEEP_CUE_PATTERNS.some((re) => re.test(norm2));
|
|
16990
18127
|
}
|
|
16991
18128
|
var DisabledAmdRuntime = class {
|
|
16992
18129
|
enabled = false;
|
|
@@ -19040,6 +20177,7 @@ function toolAuditObserver(emit) {
|
|
|
19040
20177
|
}
|
|
19041
20178
|
|
|
19042
20179
|
// src/agent.ts
|
|
20180
|
+
init_resilient_llm();
|
|
19043
20181
|
init_helper_models();
|
|
19044
20182
|
var FLOW_HANGUP_GRACE_MS = Number(process.env["VL_FLOW_HANGUP_GRACE_MS"] ?? "1200");
|
|
19045
20183
|
function isLiveKitChildProcess() {
|
|
@@ -19201,6 +20339,8 @@ var Agent = class {
|
|
|
19201
20339
|
flowBootFailure = "worker_identity_refused";
|
|
19202
20340
|
}
|
|
19203
20341
|
let flowAgentName = null;
|
|
20342
|
+
let flowAskContext;
|
|
20343
|
+
const bootNotes = [];
|
|
19204
20344
|
let loadedPlan = null;
|
|
19205
20345
|
const planIdValue = stringValue(dispatchMdEarly["planId"]);
|
|
19206
20346
|
if (planIdValue && sdkClient) {
|
|
@@ -19289,9 +20429,14 @@ var Agent = class {
|
|
|
19289
20429
|
} else {
|
|
19290
20430
|
const graphOn = process.env["VL_FLOW_GRAPH_RUNTIME"] !== "0" && !!schema.program;
|
|
19291
20431
|
const schemaToolExecutor = buildGraphResolvers(callCredential)?.toolExecutor;
|
|
20432
|
+
const personaBoot = await resolveFlowPersona(flowAgentId, () => sdkClient.agents.getConfig(flowAgentId), (kind, data) => {
|
|
20433
|
+
console.warn(`[agent] ${kind}: the flow's published config couldn't be read; the default persona runs`, data);
|
|
20434
|
+
bootNotes.push({ kind, data: { ...data } });
|
|
20435
|
+
});
|
|
20436
|
+
flowAskContext = personaBoot.askContext;
|
|
19292
20437
|
const parts = processSchemaToAgentParts(schema, {
|
|
19293
20438
|
...schemaToolExecutor ? { toolExecutor: schemaToolExecutor } : {},
|
|
19294
|
-
|
|
20439
|
+
basePrompt: personaBoot.basePrompt,
|
|
19295
20440
|
...graphOn && schema.program ? { graphNodeIds: new Set(Object.keys(schema.program.nodes)) } : {}
|
|
19296
20441
|
});
|
|
19297
20442
|
effectiveConfig = {
|
|
@@ -19316,11 +20461,12 @@ var Agent = class {
|
|
|
19316
20461
|
sdkClient,
|
|
19317
20462
|
flowAgentId,
|
|
19318
20463
|
effectiveConfig,
|
|
19319
|
-
void 0
|
|
20464
|
+
void 0,
|
|
20465
|
+
personaBoot.unavailable ? void 0 : { stored: personaBoot.stored }
|
|
19320
20466
|
)).config;
|
|
19321
20467
|
flowAgentName = await flowAgentDisplayName(
|
|
19322
20468
|
schema,
|
|
19323
|
-
async () => (await sdkClient.agents.getConfig(flowAgentId))?.name ?? null
|
|
20469
|
+
async () => personaBoot.unavailable ? (await sdkClient.agents.getConfig(flowAgentId))?.name ?? null : personaBoot.stored?.name ?? null
|
|
19324
20470
|
);
|
|
19325
20471
|
if (graphOn && schema.program) {
|
|
19326
20472
|
graphProgram = schema.program;
|
|
@@ -19372,7 +20518,11 @@ var Agent = class {
|
|
|
19372
20518
|
void trackWrite(sdkClient.calls.appendEvent(opEventCallId, { kind, payload })).catch(() => {
|
|
19373
20519
|
});
|
|
19374
20520
|
};
|
|
20521
|
+
if (isResilientLLM(pipeline.llm)) {
|
|
20522
|
+
pipeline.llm.onIncident((incident) => emitOpEvent(`engine.llm.${incident.kind}`, { ...incident }));
|
|
20523
|
+
}
|
|
19375
20524
|
const graphEvents = graphMode ? withOpEventFanout(createGraphEvents(processRt), emitOpEvent) : null;
|
|
20525
|
+
recordBootNotes(bootNotes, graphEvents, processRt);
|
|
19376
20526
|
const isOutbound = stringValue(dispatchMdEarly["direction"]) === "outbound";
|
|
19377
20527
|
const perCallAmd = stringValue(dispatchMdEarly["amd"]);
|
|
19378
20528
|
const amdMode = perCallAmd === "off" || perCallAmd === "detect" || perCallAmd === "detectMessageEnd" ? perCallAmd : effectiveConfig.outbound?.amd ?? "off";
|
|
@@ -20187,7 +21337,9 @@ ${callIntent}`
|
|
|
20187
21337
|
// Manual turn detection never runs SecureAgent.onUserTurnCompleted: each caller turn is guarded and sealed
|
|
20188
21338
|
// as the stream admits it, before it joins the model's conversation (burn-down G-25, security §B.7). The cast:
|
|
20189
21339
|
// lkAgent IS the SecureAgent when lkAgentIsSecure, whose factory type is deliberately loose (secure-agent.ts).
|
|
20190
|
-
...security && securityCallContext && lkAgentIsSecure ? { security: flowTurnSecurity(lkAgent, security, securityCallContext) } : {}
|
|
21340
|
+
...security && securityCallContext && lkAgentIsSecure ? { security: flowTurnSecurity(lkAgent, security, securityCallContext) } : {},
|
|
21341
|
+
// the published persona / routing / language the ask steps' pre-built lines are checked against (G-48)
|
|
21342
|
+
...flowAskContext ? { askContext: flowAskContext } : {}
|
|
20191
21343
|
});
|
|
20192
21344
|
if (outcome.kind === "error") {
|
|
20193
21345
|
console.error("[agent] flow graph run errored", {
|
|
@@ -20324,9 +21476,9 @@ function sharedBrainPubSub() {
|
|
|
20324
21476
|
}
|
|
20325
21477
|
return _brainPubSub;
|
|
20326
21478
|
}
|
|
20327
|
-
async function applyStoredPipeline(client, agentId, base, codeMode) {
|
|
21479
|
+
async function applyStoredPipeline(client, agentId, base, codeMode, prefetched) {
|
|
20328
21480
|
try {
|
|
20329
|
-
const stored = await client.agents.getConfig(agentId);
|
|
21481
|
+
const stored = prefetched ? prefetched.stored : await client.agents.getConfig(agentId);
|
|
20330
21482
|
if (!stored) return { config: base, brain: "none" };
|
|
20331
21483
|
const mode = codeMode ?? stored.mode;
|
|
20332
21484
|
const providers = /* @__PURE__ */ new Set();
|