@voicelayer/sdk 0.1.11 → 0.2.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 +283 -0
- package/dist/brain/index.js +3502 -0
- package/dist/index.d.ts +1579 -1645
- package/dist/index.js +7450 -5277
- package/dist/runtime/text-session.d.ts +2 -1
- package/dist/runtime/text-session.js +1915 -98
- package/dist/{text-session-D1j8nQac.d.ts → text-session-BqMWaNsN.d.ts} +361 -272
- package/package.json +9 -4
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
|
+
import 'dns';
|
|
5
|
+
import 'net';
|
|
6
|
+
import '@opentelemetry/api';
|
|
3
7
|
import 'quickjs-emscripten';
|
|
4
8
|
import { setup, createActor } from 'xstate';
|
|
5
9
|
import { randomUUID } from 'crypto';
|
|
@@ -14,6 +18,18 @@ var __export = (target, all) => {
|
|
|
14
18
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
19
|
};
|
|
16
20
|
|
|
21
|
+
// src/runtime/graph/log.ts
|
|
22
|
+
function graphLog(event, fields) {
|
|
23
|
+
console.log(`[graph] ${event}`, { ...fields, t: Date.now() });
|
|
24
|
+
}
|
|
25
|
+
function graphWarn(event, fields) {
|
|
26
|
+
console.warn(`[graph] ${event}`, { ...fields, t: Date.now() });
|
|
27
|
+
}
|
|
28
|
+
var init_log = __esm({
|
|
29
|
+
"src/runtime/graph/log.ts"() {
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
17
33
|
// src/runtime/graph/graph-model.ts
|
|
18
34
|
function buildGraphModel(program) {
|
|
19
35
|
return {
|
|
@@ -43,11 +59,100 @@ var init_graph_model = __esm({
|
|
|
43
59
|
}
|
|
44
60
|
});
|
|
45
61
|
|
|
62
|
+
// ../contracts/src/effect-stack.ts
|
|
63
|
+
var EffectStack;
|
|
64
|
+
var init_effect_stack = __esm({
|
|
65
|
+
"../contracts/src/effect-stack.ts"() {
|
|
66
|
+
EffectStack = class {
|
|
67
|
+
#disposers = [];
|
|
68
|
+
#disposed = false;
|
|
69
|
+
#draining = false;
|
|
70
|
+
#drain = null;
|
|
71
|
+
#onError;
|
|
72
|
+
constructor(options = {}) {
|
|
73
|
+
this.#onError = options.onDisposeError ?? ((error) => {
|
|
74
|
+
console.error("[EffectStack] disposer threw during teardown", error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register an acquire's inverse. Returns a handle that reverts THIS effect
|
|
79
|
+
* early (at most once). The handle is a no-op after it has fired and after
|
|
80
|
+
* dispose() has already drained the disposer.
|
|
81
|
+
*/
|
|
82
|
+
defer(disposer) {
|
|
83
|
+
if (this.#disposed && !this.#draining) {
|
|
84
|
+
const ran = this.#run(disposer);
|
|
85
|
+
return async () => {
|
|
86
|
+
await ran;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
this.#disposers.push(disposer);
|
|
90
|
+
let fired = false;
|
|
91
|
+
return async () => {
|
|
92
|
+
if (fired) return;
|
|
93
|
+
fired = true;
|
|
94
|
+
const i = this.#disposers.lastIndexOf(disposer);
|
|
95
|
+
if (i < 0) return;
|
|
96
|
+
this.#disposers.splice(i, 1);
|
|
97
|
+
await this.#run(disposer);
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** Number of pending disposers. */
|
|
101
|
+
get size() {
|
|
102
|
+
return this.#disposers.length;
|
|
103
|
+
}
|
|
104
|
+
get disposed() {
|
|
105
|
+
return this.#disposed;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Run every pending disposer in LIFO order, each awaited before the next.
|
|
109
|
+
* Idempotent AND concurrency-safe (contract 3): overlapping calls all await
|
|
110
|
+
* the one in-flight drain. A disposer that registers another disposer during
|
|
111
|
+
* teardown is drained too, in LIFO position.
|
|
112
|
+
*/
|
|
113
|
+
async dispose() {
|
|
114
|
+
return this.#drain ??= this.#runDrain();
|
|
115
|
+
}
|
|
116
|
+
async #runDrain() {
|
|
117
|
+
this.#disposed = true;
|
|
118
|
+
this.#draining = true;
|
|
119
|
+
try {
|
|
120
|
+
for (let next = this.#disposers.pop(); next !== void 0; next = this.#disposers.pop()) {
|
|
121
|
+
await this.#run(next);
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
this.#draining = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Enables `await using stack = new EffectStack()` on hosts that support it. */
|
|
128
|
+
async [Symbol.asyncDispose]() {
|
|
129
|
+
await this.dispose();
|
|
130
|
+
}
|
|
131
|
+
async #run(disposer) {
|
|
132
|
+
try {
|
|
133
|
+
await disposer();
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.#onError(error);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
46
142
|
// ../contracts/src/primitive.ts
|
|
47
143
|
var BasePrimitive;
|
|
48
144
|
var init_primitive = __esm({
|
|
49
145
|
"../contracts/src/primitive.ts"() {
|
|
146
|
+
init_effect_stack();
|
|
50
147
|
BasePrimitive = class {
|
|
148
|
+
/**
|
|
149
|
+
* Instance-lifetime effects. Subclasses that acquire a connection, queue, or
|
|
150
|
+
* timer in their constructor register its inverse here via
|
|
151
|
+
* `this.effects.defer(...)`; `[Symbol.asyncDispose]()` replays them LIFO. This
|
|
152
|
+
* is the structural fix for "a primitive that opens a resource cannot close
|
|
153
|
+
* it" (GROUNDING F1) — per-call resources still use onCallEnd.
|
|
154
|
+
*/
|
|
155
|
+
effects = new EffectStack();
|
|
51
156
|
async onError(err, _ctx) {
|
|
52
157
|
return {
|
|
53
158
|
kind: "fail",
|
|
@@ -81,6 +186,10 @@ var init_primitive = __esm({
|
|
|
81
186
|
return () => {
|
|
82
187
|
};
|
|
83
188
|
}
|
|
189
|
+
/** Release instance-lifetime resources. Idempotent. */
|
|
190
|
+
async [Symbol.asyncDispose]() {
|
|
191
|
+
await this.effects.dispose();
|
|
192
|
+
}
|
|
84
193
|
};
|
|
85
194
|
}
|
|
86
195
|
});
|
|
@@ -255,7 +364,13 @@ var init_flow_program = __esm({
|
|
|
255
364
|
droppedEdges: z.array(z.string().max(128)).max(256),
|
|
256
365
|
// Node ids needing external plumbing not wired yet (rag/classify/code) — the
|
|
257
366
|
// interpreter passes through them.
|
|
258
|
-
unsupportedNodes: z.array(z.string().max(128)).max(256)
|
|
367
|
+
unsupportedNodes: z.array(z.string().max(128)).max(256),
|
|
368
|
+
// Port-less edges out of a conversational node (llm/ask/playbook) that carry
|
|
369
|
+
// a display `label` but no `condition` — compiled as the UNCONDITIONAL
|
|
370
|
+
// default (the label never routes). Mirrors validateFlowGraph's
|
|
371
|
+
// `edge_label_not_condition` warning. Optional: programs stored before this
|
|
372
|
+
// field existed must still pass the read-side parse.
|
|
373
|
+
labelOnlyEdges: z.array(z.string().max(128)).max(256).optional()
|
|
259
374
|
});
|
|
260
375
|
FlowProgram = z.object({
|
|
261
376
|
v: z.literal(1),
|
|
@@ -372,6 +487,12 @@ var init_agents = __esm({
|
|
|
372
487
|
readiness: z.array(ReadinessReport).max(64).optional(),
|
|
373
488
|
metadata: AgentMetadata.optional(),
|
|
374
489
|
processSchema: ProcessSchemaDTO.nullable().optional(),
|
|
490
|
+
// A code-first agent's base prompt (its instructions live in code, not the DB).
|
|
491
|
+
// Synced here so a generic worker can run it over text (P2). Omit for flow agents.
|
|
492
|
+
// Cap matches AgentConfig.systemPrompt (64k) so a large-but-valid prompt doesn't
|
|
493
|
+
// fail registration outright (which agent.ts swallows → agent runs but never
|
|
494
|
+
// registers/heartbeats).
|
|
495
|
+
prompt: z.string().max(64e3).nullable().optional(),
|
|
375
496
|
// Opt into MCP exposure. When true the API upserts a matching agent_config
|
|
376
497
|
// row keyed by (project_id, name, environment) so MCP / dashboard / REST
|
|
377
498
|
// can list and inspect this agent's identity. When false / omitted, the
|
|
@@ -391,6 +512,13 @@ var init_agents = __esm({
|
|
|
391
512
|
lastSeenAt: z.string().datetime().nullable(),
|
|
392
513
|
metadata: AgentMetadata,
|
|
393
514
|
processSchema: ProcessSchemaDTO.nullable().optional(),
|
|
515
|
+
// Whether this agent can run through the engine over text: a flow agent (its
|
|
516
|
+
// compiled graph rides in processSchema.program) OR a code-first agent whose
|
|
517
|
+
// base prompt was synced on register. Drives the dashboard's chat gate so it
|
|
518
|
+
// no longer hard-codes "flow only". Computed server-side; not stored. OPTIONAL
|
|
519
|
+
// on the wire so a new worker validating a response from an OLD api pod during
|
|
520
|
+
// a rolling deploy doesn't fail register/heartbeat on a missing field.
|
|
521
|
+
textRunnable: z.boolean().optional(),
|
|
394
522
|
createdAt: z.string().datetime(),
|
|
395
523
|
updatedAt: z.string().datetime()
|
|
396
524
|
});
|
|
@@ -400,6 +528,9 @@ var init_agents = __esm({
|
|
|
400
528
|
z.object({
|
|
401
529
|
processSchema: ProcessSchemaDTO.nullable()
|
|
402
530
|
});
|
|
531
|
+
z.object({
|
|
532
|
+
prompt: z.string().nullable()
|
|
533
|
+
});
|
|
403
534
|
z.object({
|
|
404
535
|
agent: AgentDTO,
|
|
405
536
|
// Server-controlled cadence so we can tune without an SDK release.
|
|
@@ -486,7 +617,10 @@ var init_consultation = __esm({
|
|
|
486
617
|
consultationId: z.string().uuid(),
|
|
487
618
|
guidance: z.string().min(1).max(4e3),
|
|
488
619
|
// Optional bridge phrase the agent may speak before delivering guidance.
|
|
489
|
-
resumePhrase: z.string().max(500).optional()
|
|
620
|
+
resumePhrase: z.string().max(500).optional(),
|
|
621
|
+
// G-5: the API's id for this command; the agent confirms it (CallControlAck). Optional so a command from an older
|
|
622
|
+
// API is still acted on (just not confirmed).
|
|
623
|
+
commandId: z.string().uuid().optional()
|
|
490
624
|
});
|
|
491
625
|
ConsultationPolicy = z.object({
|
|
492
626
|
enabled: z.boolean().default(true),
|
|
@@ -511,7 +645,7 @@ var init_consultation = __esm({
|
|
|
511
645
|
});
|
|
512
646
|
}
|
|
513
647
|
});
|
|
514
|
-
var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
|
|
648
|
+
var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, commandId, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
|
|
515
649
|
var init_call_events = __esm({
|
|
516
650
|
"../contracts/src/call-events.ts"() {
|
|
517
651
|
init_consultation();
|
|
@@ -536,7 +670,7 @@ var init_call_events = __esm({
|
|
|
536
670
|
"dtmf.received",
|
|
537
671
|
"dtmf.sent"
|
|
538
672
|
]);
|
|
539
|
-
OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
|
|
673
|
+
OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify|engine)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
|
|
540
674
|
OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
|
|
541
675
|
z.object({
|
|
542
676
|
reason: z.string().optional(),
|
|
@@ -581,8 +715,10 @@ var init_call_events = __esm({
|
|
|
581
715
|
ts: z.string().datetime()
|
|
582
716
|
});
|
|
583
717
|
z.enum(["say", "hangup", "dtmf", "instruct", "guidance"]);
|
|
718
|
+
commandId = z.string().uuid().optional();
|
|
584
719
|
SayCommand = z.object({
|
|
585
720
|
kind: z.literal("say"),
|
|
721
|
+
commandId,
|
|
586
722
|
text: z.string().min(1),
|
|
587
723
|
// 'interrupt' replaces any in-flight TTS; 'queue' waits for current to end.
|
|
588
724
|
mode: z.enum(["interrupt", "queue"]).default("interrupt"),
|
|
@@ -590,14 +726,17 @@ var init_call_events = __esm({
|
|
|
590
726
|
});
|
|
591
727
|
HangupCommand = z.object({
|
|
592
728
|
kind: z.literal("hangup"),
|
|
729
|
+
commandId,
|
|
593
730
|
reason: z.string().optional()
|
|
594
731
|
});
|
|
595
732
|
DtmfCommand = z.object({
|
|
596
733
|
kind: z.literal("dtmf"),
|
|
734
|
+
commandId,
|
|
597
735
|
digits: z.string().regex(/^[0-9*#]+$/)
|
|
598
736
|
});
|
|
599
737
|
InstructCommand = z.object({
|
|
600
738
|
kind: z.literal("instruct"),
|
|
739
|
+
commandId,
|
|
601
740
|
text: z.string().min(1)
|
|
602
741
|
});
|
|
603
742
|
z.discriminatedUnion("kind", [
|
|
@@ -607,6 +746,14 @@ var init_call_events = __esm({
|
|
|
607
746
|
InstructCommand,
|
|
608
747
|
GuidanceCommand
|
|
609
748
|
]);
|
|
749
|
+
z.object({
|
|
750
|
+
commandId: z.string().uuid(),
|
|
751
|
+
outcome: z.enum(["applied", "refused", "failed"]),
|
|
752
|
+
// machine code: output_blocked · not_supported · unknown_consultation · no_consultation · invalid_command ·
|
|
753
|
+
// say_unavailable · dtmf_unavailable
|
|
754
|
+
code: z.string().max(64).optional(),
|
|
755
|
+
detail: z.string().max(500).optional()
|
|
756
|
+
});
|
|
610
757
|
}
|
|
611
758
|
});
|
|
612
759
|
var VoiceConfig, ModelConfig, SttConfig, RealtimeConfig, PipelineMode, ToolBinding, ProcessFieldType, ProcessFieldSpec, ProcessCompleteWhen, ProcessBackendAckSpec, ProcessSpec, TriggerConditionSpec, TriggerActionSpec, TriggerSpec, RequiredInfoField, AgentConfig;
|
|
@@ -1022,6 +1169,113 @@ var init_forms = __esm({
|
|
|
1022
1169
|
"../contracts/src/forms.ts"() {
|
|
1023
1170
|
}
|
|
1024
1171
|
});
|
|
1172
|
+
var AgentConfigVersionDTO, AgentConfigDiffField, AgentConfigChange;
|
|
1173
|
+
var init_agent_versions = __esm({
|
|
1174
|
+
"../contracts/src/agent-versions.ts"() {
|
|
1175
|
+
init_agent_config();
|
|
1176
|
+
init_agents();
|
|
1177
|
+
init_consultation();
|
|
1178
|
+
z.object({
|
|
1179
|
+
systemPrompt: z.string().max(64e3),
|
|
1180
|
+
routingInstructions: z.string().max(32e3).nullable(),
|
|
1181
|
+
voice: VoiceConfig,
|
|
1182
|
+
model: ModelConfig,
|
|
1183
|
+
stt: SttConfig.nullable(),
|
|
1184
|
+
mode: PipelineMode,
|
|
1185
|
+
realtime: RealtimeConfig.nullable(),
|
|
1186
|
+
language: z.string().min(2).max(16),
|
|
1187
|
+
tools: z.array(ToolBinding).max(64),
|
|
1188
|
+
consultation: ConsultationPolicy,
|
|
1189
|
+
tags: z.array(z.string().max(64)).max(32).nullable(),
|
|
1190
|
+
brainConnectorId: z.string().uuid().nullable(),
|
|
1191
|
+
flowVersion: z.number().int().nullable(),
|
|
1192
|
+
processSchema: ProcessSchemaDTO.nullable()
|
|
1193
|
+
});
|
|
1194
|
+
z.enum(["published", "draft"]);
|
|
1195
|
+
AgentConfigVersionDTO = z.object({
|
|
1196
|
+
id: z.string().uuid(),
|
|
1197
|
+
version: z.number().int().min(1),
|
|
1198
|
+
createdAt: z.string().datetime(),
|
|
1199
|
+
// 'user:<clerkId>' · 'api_key:<id>' · 'system:backfill' · 'system:deploy' · 'system:register'
|
|
1200
|
+
createdBy: z.string().nullable(),
|
|
1201
|
+
note: z.string().nullable(),
|
|
1202
|
+
flowVersion: z.number().int().nullable(),
|
|
1203
|
+
isPublished: z.boolean()
|
|
1204
|
+
});
|
|
1205
|
+
z.object({
|
|
1206
|
+
versions: z.array(AgentConfigVersionDTO),
|
|
1207
|
+
publishedVersion: z.number().int().min(1)
|
|
1208
|
+
});
|
|
1209
|
+
z.object({
|
|
1210
|
+
note: z.string().trim().max(500).optional()
|
|
1211
|
+
});
|
|
1212
|
+
z.object({
|
|
1213
|
+
version: AgentConfigVersionDTO
|
|
1214
|
+
});
|
|
1215
|
+
AgentConfigDiffField = z.enum([
|
|
1216
|
+
"systemPrompt",
|
|
1217
|
+
"routingInstructions",
|
|
1218
|
+
"mode",
|
|
1219
|
+
"model",
|
|
1220
|
+
"voice",
|
|
1221
|
+
"stt",
|
|
1222
|
+
"realtime",
|
|
1223
|
+
"language",
|
|
1224
|
+
"tools",
|
|
1225
|
+
"brainConnectorId",
|
|
1226
|
+
"consultation",
|
|
1227
|
+
"tags",
|
|
1228
|
+
"flowVersion",
|
|
1229
|
+
"processSchema"
|
|
1230
|
+
]);
|
|
1231
|
+
AgentConfigChange = z.object({
|
|
1232
|
+
field: AgentConfigDiffField,
|
|
1233
|
+
label: z.string(),
|
|
1234
|
+
/** The published value in plain words. */
|
|
1235
|
+
from: z.string(),
|
|
1236
|
+
/** The draft value in plain words. */
|
|
1237
|
+
to: z.string(),
|
|
1238
|
+
/** True for long text (prompts) — the sheet renders it in a scrollable block. */
|
|
1239
|
+
longText: z.boolean()
|
|
1240
|
+
});
|
|
1241
|
+
z.object({
|
|
1242
|
+
published: z.object({
|
|
1243
|
+
version: z.number().int().min(1),
|
|
1244
|
+
createdAt: z.string().datetime(),
|
|
1245
|
+
note: z.string().nullable()
|
|
1246
|
+
}),
|
|
1247
|
+
hasChanges: z.boolean(),
|
|
1248
|
+
changes: z.array(AgentConfigChange)
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
// ../contracts/src/providers.ts
|
|
1254
|
+
function providerIdentity(id) {
|
|
1255
|
+
if (!id) return void 0;
|
|
1256
|
+
return PROVIDERS[PROVIDER_ALIASES[id] ?? id];
|
|
1257
|
+
}
|
|
1258
|
+
function byokProviders() {
|
|
1259
|
+
return Object.values(PROVIDERS).filter((p) => p.byok);
|
|
1260
|
+
}
|
|
1261
|
+
var PROVIDERS, PROVIDER_ALIASES;
|
|
1262
|
+
var init_providers = __esm({
|
|
1263
|
+
"../contracts/src/providers.ts"() {
|
|
1264
|
+
PROVIDERS = {
|
|
1265
|
+
openai: { id: "openai", label: "OpenAI", capabilities: ["llm", "tts", "realtime"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-..." },
|
|
1266
|
+
anthropic: { id: "anthropic", label: "Anthropic", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-ant-..." },
|
|
1267
|
+
deepgram: { id: "deepgram", label: "Deepgram", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Deepgram API key" },
|
|
1268
|
+
elevenlabs: { id: "elevenlabs", label: "ElevenLabs", capabilities: ["tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "ElevenLabs API key" },
|
|
1269
|
+
cartesia: { id: "cartesia", label: "Cartesia", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Cartesia API key" },
|
|
1270
|
+
assemblyai: { id: "assemblyai", label: "AssemblyAI", capabilities: ["stt"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AssemblyAI API key" },
|
|
1271
|
+
google: { id: "google", label: "Google (Gemini)", capabilities: ["llm", "realtime"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AIza..." },
|
|
1272
|
+
groq: { id: "groq", label: "Groq", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "gsk_..." },
|
|
1273
|
+
// Runtime-only providers (no external key): local VAD.
|
|
1274
|
+
silero: { id: "silero", label: "Silero", capabilities: ["vad"], byok: false, supportsBaseUrl: false }
|
|
1275
|
+
};
|
|
1276
|
+
PROVIDER_ALIASES = { gemini: "google" };
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1025
1279
|
var ProviderKind, CatalogVoice, CatalogModel, CatalogProvider, CostEstimateAssumptions, CostEstimatePerMinute, CostEstimateBilledPerMinute;
|
|
1026
1280
|
var init_model_catalog = __esm({
|
|
1027
1281
|
"../contracts/src/model-catalog.ts"() {
|
|
@@ -1419,6 +1673,8 @@ var init_numbers = __esm({
|
|
|
1419
1673
|
agentId: z.string().uuid().nullable(),
|
|
1420
1674
|
// Environments pin (draft/live). null = no pin → live (today's behaviour).
|
|
1421
1675
|
agentConfigEnv: z.enum(["draft", "live"]).nullable(),
|
|
1676
|
+
// "Agent answers texts": the bound agent replies to inbound SMS (burn-down G-1). Off by default.
|
|
1677
|
+
smsAgentReplies: z.boolean(),
|
|
1422
1678
|
status: PhoneNumberStatus,
|
|
1423
1679
|
friendlyName: z.string().nullable(),
|
|
1424
1680
|
region: z.string().nullable(),
|
|
@@ -1442,6 +1698,7 @@ var init_numbers = __esm({
|
|
|
1442
1698
|
// null detaches.
|
|
1443
1699
|
agentId: z.string().uuid().nullable()
|
|
1444
1700
|
});
|
|
1701
|
+
z.object({ enabled: z.boolean() });
|
|
1445
1702
|
}
|
|
1446
1703
|
});
|
|
1447
1704
|
var init_project_limits = __esm({
|
|
@@ -1548,6 +1805,12 @@ var init_compliance = __esm({
|
|
|
1548
1805
|
ComplianceConfig.partial();
|
|
1549
1806
|
}
|
|
1550
1807
|
});
|
|
1808
|
+
|
|
1809
|
+
// ../contracts/src/sms-keywords.ts
|
|
1810
|
+
var init_sms_keywords = __esm({
|
|
1811
|
+
"../contracts/src/sms-keywords.ts"() {
|
|
1812
|
+
}
|
|
1813
|
+
});
|
|
1551
1814
|
var init_call_recording = __esm({
|
|
1552
1815
|
"../contracts/src/call-recording.ts"() {
|
|
1553
1816
|
z.object({
|
|
@@ -1584,10 +1847,16 @@ var init_outbound = __esm({
|
|
|
1584
1847
|
var ConnectionAuthType, ConnectionStatus;
|
|
1585
1848
|
var init_connection = __esm({
|
|
1586
1849
|
"../contracts/src/connection.ts"() {
|
|
1850
|
+
init_providers();
|
|
1587
1851
|
ConnectionAuthType = z.enum(["oauth2", "api_key", "bearer", "basic", "none"]);
|
|
1588
1852
|
ConnectionStatus = z.enum(["active", "revoked", "error"]);
|
|
1589
1853
|
z.object({
|
|
1590
1854
|
v: z.number().int(),
|
|
1855
|
+
// Key id (short fingerprint) of the key that encrypted this envelope — lets the
|
|
1856
|
+
// cipher pick the right key from its ring so CREDENTIAL_ENCRYPTION_KEY can rotate
|
|
1857
|
+
// in-band (Tier-1 item 4). Optional/absent on legacy v1 envelopes, which decrypt
|
|
1858
|
+
// with the designated legacy key.
|
|
1859
|
+
kid: z.string().optional(),
|
|
1591
1860
|
iv: z.string(),
|
|
1592
1861
|
ct: z.string(),
|
|
1593
1862
|
tag: z.string()
|
|
@@ -1625,6 +1894,12 @@ var init_connection = __esm({
|
|
|
1625
1894
|
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
1626
1895
|
message: "nothing to update"
|
|
1627
1896
|
});
|
|
1897
|
+
byokProviders().map((p) => ({
|
|
1898
|
+
id: p.id,
|
|
1899
|
+
label: p.label,
|
|
1900
|
+
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
1901
|
+
supportsBaseUrl: p.supportsBaseUrl
|
|
1902
|
+
}));
|
|
1628
1903
|
}
|
|
1629
1904
|
});
|
|
1630
1905
|
var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
|
|
@@ -1958,6 +2233,7 @@ var init_flow_enrichment = __esm({
|
|
|
1958
2233
|
var init_flow_compile = __esm({
|
|
1959
2234
|
"../contracts/src/flow-compile.ts"() {
|
|
1960
2235
|
init_flow_enrichment();
|
|
2236
|
+
init_flow_validate();
|
|
1961
2237
|
}
|
|
1962
2238
|
});
|
|
1963
2239
|
var EnvironmentSpec;
|
|
@@ -2102,6 +2378,1057 @@ var init_playbook_compile = __esm({
|
|
|
2102
2378
|
"../contracts/src/playbook-compile.ts"() {
|
|
2103
2379
|
}
|
|
2104
2380
|
});
|
|
2381
|
+
var EngineSeamBinding, EnginePluginDTO;
|
|
2382
|
+
var init_engine = __esm({
|
|
2383
|
+
"../contracts/src/engine.ts"() {
|
|
2384
|
+
EngineSeamBinding = z.object({
|
|
2385
|
+
key: z.string(),
|
|
2386
|
+
bound: z.boolean()
|
|
2387
|
+
});
|
|
2388
|
+
EnginePluginDTO = z.object({
|
|
2389
|
+
name: z.string(),
|
|
2390
|
+
provides: z.array(EngineSeamBinding),
|
|
2391
|
+
injects: z.array(z.string())
|
|
2392
|
+
});
|
|
2393
|
+
z.object({
|
|
2394
|
+
plugins: z.array(EnginePluginDTO)
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
});
|
|
2398
|
+
|
|
2399
|
+
// ../contracts/src/identity.ts
|
|
2400
|
+
var init_identity = __esm({
|
|
2401
|
+
"../contracts/src/identity.ts"() {
|
|
2402
|
+
}
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2405
|
+
// ../contracts/src/interaction.ts
|
|
2406
|
+
var init_interaction = __esm({
|
|
2407
|
+
"../contracts/src/interaction.ts"() {
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2411
|
+
// ../contracts/src/channel-connector/capability-state.ts
|
|
2412
|
+
var CONDITION_CLASSES, CONDITION_TYPES, SURFACES, TRANSPORT_KINDS, AGENT_INITIATION, TEMPLATE_REQUIREMENT, WINDOW_OUTSIDE, PEER_ID_SCOPES, RECEIPT_KINDS;
|
|
2413
|
+
var init_capability_state = __esm({
|
|
2414
|
+
"../contracts/src/channel-connector/capability-state.ts"() {
|
|
2415
|
+
CONDITION_CLASSES = ["authority", "operational", "observability"];
|
|
2416
|
+
CONDITION_TYPES = [
|
|
2417
|
+
{ id: "CredentialsValid", defaultClass: "authority", summary: "credential lease resolves and is not expired/revoked" },
|
|
2418
|
+
{ id: "ProviderAccountGood", defaultClass: "authority", summary: "provider account / sender standing is not suspended or restricted" },
|
|
2419
|
+
{ id: "RegistrationApproved", defaultClass: "authority", summary: "required registration / approval is current" },
|
|
2420
|
+
{ id: "ResourceOwnershipConfirmed", defaultClass: "authority", summary: "the native object is confirmed to belong to this principal" },
|
|
2421
|
+
{ id: "InstallationPresent", defaultClass: "authority", summary: "the container installation exists and is not revoked" },
|
|
2422
|
+
{ id: "PermissionsHeld", defaultClass: "authority", summary: "required provider permissions/scopes/intents are held" },
|
|
2423
|
+
{ id: "ConsentResolved", defaultClass: "authority", summary: "consent verdict available (ch. 02; not_evaluated until it ships)" },
|
|
2424
|
+
{ id: "WindowOpen", defaultClass: "authority", summary: "the reply window for the peer is open" },
|
|
2425
|
+
{ id: "TemplateAvailable", defaultClass: "authority", summary: "an approved template exists for the operation" },
|
|
2426
|
+
{ id: "PeerReachable", defaultClass: "authority", summary: "the peer can be reached on this channel (e.g. RCS capability, not blocked)" },
|
|
2427
|
+
{ id: "WebhookRegistered", defaultClass: "operational", summary: "the provider has our ingress registered" },
|
|
2428
|
+
{ id: "InboundVerificationReady", defaultClass: "authority", summary: "the material the declared inbound-auth strategy needs (for the selected authorization method) resolves \u2014 REST auth alone never satisfies it (#306 L4)" },
|
|
2429
|
+
{ id: "WebhookHealthy", defaultClass: "operational", summary: "recent verified inbound / delivery health" },
|
|
2430
|
+
{ id: "TransportConnected", defaultClass: "operational", summary: "a transport lease is live (gateway / long-poll / pub-sub)" },
|
|
2431
|
+
{ id: "ReconcileFresh", defaultClass: "operational", summary: "the last reconcile is within its window" },
|
|
2432
|
+
{ id: "ProviderLatency", defaultClass: "observability", summary: "provider latency / error-rate telemetry" }
|
|
2433
|
+
];
|
|
2434
|
+
new Set(CONDITION_TYPES.map((c) => c.id));
|
|
2435
|
+
SURFACES = ["direct", "group", "channel", "thread", "broadcast"];
|
|
2436
|
+
TRANSPORT_KINDS = ["webhook", "long_poll", "gateway", "pubsub", "http_interactions"];
|
|
2437
|
+
AGENT_INITIATION = ["always", "installed_only", "opt_in_required", "template_only", "never"];
|
|
2438
|
+
TEMPLATE_REQUIREMENT = ["never", "outside_window", "always"];
|
|
2439
|
+
WINDOW_OUTSIDE = ["tagged_only", "template_only", "blocked"];
|
|
2440
|
+
PEER_ID_SCOPES = ["global", "app_scoped", "workspace_scoped", "installation_scoped"];
|
|
2441
|
+
RECEIPT_KINDS = ["sent", "delivered", "read", "failed"];
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
// ../contracts/src/channel-connector/channels.ts
|
|
2446
|
+
function baseline(channel, partial) {
|
|
2447
|
+
return {
|
|
2448
|
+
channel,
|
|
2449
|
+
connector: null,
|
|
2450
|
+
version: "1.0",
|
|
2451
|
+
requiredPermissions: [],
|
|
2452
|
+
registration: [],
|
|
2453
|
+
readinessGates: [
|
|
2454
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2455
|
+
{ condition: "ProviderAccountGood", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2456
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 900, evidence: "runtime" }
|
|
2457
|
+
],
|
|
2458
|
+
...partial
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
var TEXT_ONLY, RICH_MESSAGING, MESSAGE_CORE, CONTAINER_GATES, CHANNELS, CHANNEL_IDS;
|
|
2462
|
+
var init_channels = __esm({
|
|
2463
|
+
"../contracts/src/channel-connector/channels.ts"() {
|
|
2464
|
+
init_capability_state();
|
|
2465
|
+
TEXT_ONLY = ["text"];
|
|
2466
|
+
RICH_MESSAGING = ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "template"];
|
|
2467
|
+
MESSAGE_CORE = ["receive.message", "send.text", "send.media", "initiate.conversation", "reply.in_window"];
|
|
2468
|
+
CONTAINER_GATES = [
|
|
2469
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2470
|
+
{ condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2471
|
+
{ condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "group.post"], ttlSec: 3600 },
|
|
2472
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
|
|
2473
|
+
];
|
|
2474
|
+
CHANNELS = [
|
|
2475
|
+
{
|
|
2476
|
+
id: "sms",
|
|
2477
|
+
displayName: "SMS",
|
|
2478
|
+
addressSpace: "phone",
|
|
2479
|
+
surfaces: ["direct"],
|
|
2480
|
+
bindingCapabilities: ["sms"],
|
|
2481
|
+
baseline: baseline("sms", {
|
|
2482
|
+
operations: [...MESSAGE_CORE, "read.receipt"],
|
|
2483
|
+
direction: { inbound: true, outbound: true },
|
|
2484
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated with registration (10DLC / TFV per country); STOP/HELP keywords mandatory" },
|
|
2485
|
+
windows: [],
|
|
2486
|
+
templates: { required: "never", reviewed: false },
|
|
2487
|
+
surfaces: ["direct"],
|
|
2488
|
+
messageTypes: TEXT_ONLY,
|
|
2489
|
+
// GSM-7 160/153, UCS-2 70/67 per segment (doc 01 §3); no body cap is a channel fact.
|
|
2490
|
+
limits: { segments: true, note: "GSM-7 160/153 \xB7 UCS-2 70/67 per segment" },
|
|
2491
|
+
receipts: ["sent", "delivered", "failed"],
|
|
2492
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2493
|
+
transport: ["webhook"],
|
|
2494
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2495
|
+
registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
|
|
2496
|
+
})
|
|
2497
|
+
},
|
|
2498
|
+
{
|
|
2499
|
+
id: "mms",
|
|
2500
|
+
displayName: "MMS",
|
|
2501
|
+
addressSpace: "phone",
|
|
2502
|
+
surfaces: ["direct", "group"],
|
|
2503
|
+
bindingCapabilities: ["mms"],
|
|
2504
|
+
baseline: baseline("mms", {
|
|
2505
|
+
operations: [...MESSAGE_CORE, "group.post"],
|
|
2506
|
+
direction: { inbound: true, outbound: true },
|
|
2507
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "as SMS; carrier MMS size caps apply" },
|
|
2508
|
+
windows: [],
|
|
2509
|
+
templates: { required: "never", reviewed: false },
|
|
2510
|
+
surfaces: ["direct", "group"],
|
|
2511
|
+
messageTypes: ["text", "image", "audio", "video", "document"],
|
|
2512
|
+
limits: { mediaBytes: 1e6, note: "carrier MMS \u22480.6\u20131 MB (doc 01 \xA73)" },
|
|
2513
|
+
receipts: ["sent", "delivered", "failed"],
|
|
2514
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2515
|
+
transport: ["webhook"],
|
|
2516
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2517
|
+
registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
|
|
2518
|
+
})
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
id: "voice",
|
|
2522
|
+
displayName: "Voice (PSTN)",
|
|
2523
|
+
addressSpace: "phone",
|
|
2524
|
+
surfaces: ["direct"],
|
|
2525
|
+
bindingCapabilities: ["voice"],
|
|
2526
|
+
baseline: baseline("voice", {
|
|
2527
|
+
operations: ["call.receive", "call.place"],
|
|
2528
|
+
direction: { inbound: true, outbound: true },
|
|
2529
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated; STIR/SHAKEN attestation; emergency addressing; trunk config" },
|
|
2530
|
+
windows: [],
|
|
2531
|
+
templates: { required: "never", reviewed: false },
|
|
2532
|
+
surfaces: ["direct"],
|
|
2533
|
+
messageTypes: [],
|
|
2534
|
+
limits: {},
|
|
2535
|
+
receipts: [],
|
|
2536
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2537
|
+
transport: ["webhook"],
|
|
2538
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" }
|
|
2539
|
+
})
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
id: "whatsapp",
|
|
2543
|
+
displayName: "WhatsApp",
|
|
2544
|
+
addressSpace: "whatsapp",
|
|
2545
|
+
surfaces: ["direct", "group"],
|
|
2546
|
+
bindingCapabilities: ["whatsapp.message"],
|
|
2547
|
+
baseline: baseline("whatsapp", {
|
|
2548
|
+
operations: [...MESSAGE_CORE, "send.template", "send.interactive", "send.reaction", "read.receipt", "typing.indicate"],
|
|
2549
|
+
direction: { inbound: true, outbound: true },
|
|
2550
|
+
initiation: { agentInitiated: "template_only", userInitiated: true, firstContactRule: "user-initiated or a reviewed template outside the 24-h window; opt-in mandatory" },
|
|
2551
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "template_only" }],
|
|
2552
|
+
templates: { required: "outside_window", reviewed: true },
|
|
2553
|
+
surfaces: ["direct", "group"],
|
|
2554
|
+
messageTypes: RICH_MESSAGING,
|
|
2555
|
+
limits: {
|
|
2556
|
+
textChars: 4096,
|
|
2557
|
+
mediaBytes: 1e8,
|
|
2558
|
+
perSecond: 80,
|
|
2559
|
+
formats: { audio: { mime: ["audio/ogg", "audio/mpeg", "audio/mp4", "audio/aac", "audio/amr"], variants: ["voice_note", "file"] } }
|
|
2560
|
+
},
|
|
2561
|
+
receipts: ["sent", "delivered", "read", "failed"],
|
|
2562
|
+
edits: { edit: false, delete: false, reactions: true, commands: false },
|
|
2563
|
+
transport: ["webhook"],
|
|
2564
|
+
identity: { peerIdKind: "whatsapp", peerIdScope: "global" },
|
|
2565
|
+
registration: [{ kind: "business_verification", blocks: ["initiate.conversation", "send.template"] }]
|
|
2566
|
+
})
|
|
2567
|
+
},
|
|
2568
|
+
{
|
|
2569
|
+
id: "instagram",
|
|
2570
|
+
displayName: "Instagram DM",
|
|
2571
|
+
addressSpace: "app",
|
|
2572
|
+
surfaces: ["direct"],
|
|
2573
|
+
bindingCapabilities: ["instagram.message"],
|
|
2574
|
+
baseline: baseline("instagram", {
|
|
2575
|
+
operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "delete.message", "read.receipt"],
|
|
2576
|
+
direction: { inbound: true, outbound: true },
|
|
2577
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "user-initiated only \u2014 the peer identifier exists only after the user messages or clicks" },
|
|
2578
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
|
|
2579
|
+
templates: { required: "never", reviewed: false },
|
|
2580
|
+
surfaces: ["direct"],
|
|
2581
|
+
messageTypes: ["text", "image", "audio", "video", "document", "sticker", "reaction", "interactive", "template"],
|
|
2582
|
+
limits: { textBytes: 1e3, mediaBytes: 25e6 },
|
|
2583
|
+
receipts: ["read"],
|
|
2584
|
+
edits: { edit: false, delete: true, reactions: true, commands: false },
|
|
2585
|
+
transport: ["webhook"],
|
|
2586
|
+
// doc 07 §3: per person × professional account, stable across apps — the
|
|
2587
|
+
// account scope is embedded in the normalized value, so the id is global.
|
|
2588
|
+
identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
|
|
2589
|
+
registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
|
|
2590
|
+
})
|
|
2591
|
+
},
|
|
2592
|
+
{
|
|
2593
|
+
id: "messenger",
|
|
2594
|
+
displayName: "Messenger",
|
|
2595
|
+
addressSpace: "app",
|
|
2596
|
+
surfaces: ["direct"],
|
|
2597
|
+
bindingCapabilities: ["messenger.message"],
|
|
2598
|
+
baseline: baseline("messenger", {
|
|
2599
|
+
operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "edit.message", "read.receipt", "typing.indicate"],
|
|
2600
|
+
direction: { inbound: true, outbound: true },
|
|
2601
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "as Instagram plus Get Started / link / ads entry points" },
|
|
2602
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
|
|
2603
|
+
templates: { required: "never", reviewed: false },
|
|
2604
|
+
surfaces: ["direct"],
|
|
2605
|
+
messageTypes: RICH_MESSAGING,
|
|
2606
|
+
limits: { mediaBytes: 25e6 },
|
|
2607
|
+
receipts: ["delivered", "read"],
|
|
2608
|
+
edits: { edit: true, delete: false, reactions: true, commands: false },
|
|
2609
|
+
transport: ["webhook"],
|
|
2610
|
+
identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
|
|
2611
|
+
registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
|
|
2612
|
+
})
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
id: "telegram",
|
|
2616
|
+
displayName: "Telegram",
|
|
2617
|
+
addressSpace: "app",
|
|
2618
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2619
|
+
bindingCapabilities: ["telegram.message"],
|
|
2620
|
+
baseline: baseline("telegram", {
|
|
2621
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "group.post", "channel.post", "thread.reply"],
|
|
2622
|
+
direction: { inbound: true, outbound: true },
|
|
2623
|
+
// Plain bot: the user must start; no reply window. The business-account
|
|
2624
|
+
// 24-h rule is an installation-level narrowing, not the channel ceiling.
|
|
2625
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the user must start the bot (/start, deep link, group add) before it may message them" },
|
|
2626
|
+
windows: [],
|
|
2627
|
+
templates: { required: "never", reviewed: false },
|
|
2628
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2629
|
+
messageTypes: ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "poll"],
|
|
2630
|
+
limits: { textChars: 4096, mediaBytes: 5e7, formats: { audio: { mime: ["audio/ogg", "audio/mpeg"], variants: ["voice_note", "file"] } } },
|
|
2631
|
+
receipts: [],
|
|
2632
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2633
|
+
transport: ["webhook", "long_poll"],
|
|
2634
|
+
identity: { peerIdKind: "telegram_user", peerIdScope: "global" }
|
|
2635
|
+
})
|
|
2636
|
+
},
|
|
2637
|
+
{
|
|
2638
|
+
id: "discord",
|
|
2639
|
+
displayName: "Discord",
|
|
2640
|
+
addressSpace: "app",
|
|
2641
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2642
|
+
bindingCapabilities: ["discord.message", "discord.command"],
|
|
2643
|
+
baseline: baseline("discord", {
|
|
2644
|
+
// The channel exposes BOTH modes (doc 04 §1): text via `send.*`, realtime
|
|
2645
|
+
// audio via `call.*`. Mode is per OPERATION — the channel id never implies it.
|
|
2646
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply", "call.receive", "call.place"],
|
|
2647
|
+
direction: { inbound: true, outbound: true },
|
|
2648
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "guild channels by permission; DMs need a mutual guild or a user install" },
|
|
2649
|
+
windows: [],
|
|
2650
|
+
templates: { required: "never", reviewed: false },
|
|
2651
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2652
|
+
messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card", "poll"],
|
|
2653
|
+
limits: { textChars: 2e3, mediaBytes: 10485760 },
|
|
2654
|
+
receipts: [],
|
|
2655
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2656
|
+
transport: ["gateway", "http_interactions"],
|
|
2657
|
+
identity: { peerIdKind: "discord_user", peerIdScope: "global" },
|
|
2658
|
+
readinessGates: [
|
|
2659
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2660
|
+
{ condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2661
|
+
{ condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "call.receive", "call.place"], ttlSec: 3600 },
|
|
2662
|
+
{ condition: "TransportConnected", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 120 }
|
|
2663
|
+
]
|
|
2664
|
+
})
|
|
2665
|
+
},
|
|
2666
|
+
{
|
|
2667
|
+
// G-3: the embeddable website chat widget. The widget key IS the identity and its evidence (a `web`-space Binding
|
|
2668
|
+
// at `widget:<keyId>`, no provider connection), so no provider readiness gate applies. The agent only ever answers
|
|
2669
|
+
// inside the visitor's own request — it never starts or pushes a message.
|
|
2670
|
+
id: "web",
|
|
2671
|
+
displayName: "Website chat",
|
|
2672
|
+
addressSpace: "web",
|
|
2673
|
+
surfaces: ["direct"],
|
|
2674
|
+
bindingCapabilities: ["web.message"],
|
|
2675
|
+
baseline: baseline("web", {
|
|
2676
|
+
operations: ["receive.message", "reply.in_window"],
|
|
2677
|
+
direction: { inbound: true, outbound: true },
|
|
2678
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the visitor opens the widget and writes first" },
|
|
2679
|
+
windows: [],
|
|
2680
|
+
templates: { required: "never", reviewed: false },
|
|
2681
|
+
surfaces: ["direct"],
|
|
2682
|
+
messageTypes: TEXT_ONLY,
|
|
2683
|
+
limits: { textChars: 8e3 },
|
|
2684
|
+
receipts: [],
|
|
2685
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2686
|
+
transport: ["http_interactions"],
|
|
2687
|
+
identity: { peerIdKind: "web_visitor", peerIdScope: "app_scoped" },
|
|
2688
|
+
readinessGates: []
|
|
2689
|
+
})
|
|
2690
|
+
},
|
|
2691
|
+
{
|
|
2692
|
+
id: "slack",
|
|
2693
|
+
displayName: "Slack",
|
|
2694
|
+
addressSpace: "app",
|
|
2695
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2696
|
+
bindingCapabilities: ["slack.message"],
|
|
2697
|
+
baseline: baseline("slack", {
|
|
2698
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply"],
|
|
2699
|
+
direction: { inbound: true, outbound: true },
|
|
2700
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "any channel the bot is in; DMs" },
|
|
2701
|
+
windows: [],
|
|
2702
|
+
templates: { required: "never", reviewed: false },
|
|
2703
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2704
|
+
messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card"],
|
|
2705
|
+
limits: { textChars: 4e3, note: "truncated at 40,000 (doc 01 \xA73)" },
|
|
2706
|
+
receipts: [],
|
|
2707
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2708
|
+
transport: ["webhook", "gateway"],
|
|
2709
|
+
identity: { peerIdKind: "slack_user", peerIdScope: "workspace_scoped" },
|
|
2710
|
+
readinessGates: CONTAINER_GATES
|
|
2711
|
+
})
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
id: "teams",
|
|
2715
|
+
displayName: "Microsoft Teams",
|
|
2716
|
+
addressSpace: "app",
|
|
2717
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2718
|
+
bindingCapabilities: ["teams.message"],
|
|
2719
|
+
baseline: baseline("teams", {
|
|
2720
|
+
operations: [...MESSAGE_CORE, "send.interactive", "edit.message", "delete.message", "channel.post", "thread.reply", "read.receipt"],
|
|
2721
|
+
direction: { inbound: true, outbound: true },
|
|
2722
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "proactive send needs an installation in scope plus a conversation reference" },
|
|
2723
|
+
windows: [],
|
|
2724
|
+
templates: { required: "never", reviewed: false },
|
|
2725
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2726
|
+
messageTypes: ["text", "image", "document", "card", "interactive", "reaction"],
|
|
2727
|
+
limits: { textBytes: 102400, note: "message payload \u2264100 KB (doc 01 \xA73)" },
|
|
2728
|
+
receipts: ["read"],
|
|
2729
|
+
edits: { edit: true, delete: true, reactions: false, commands: false },
|
|
2730
|
+
transport: ["webhook"],
|
|
2731
|
+
identity: { peerIdKind: "teams_user", peerIdScope: "installation_scoped" },
|
|
2732
|
+
readinessGates: CONTAINER_GATES
|
|
2733
|
+
})
|
|
2734
|
+
},
|
|
2735
|
+
{
|
|
2736
|
+
id: "rcs",
|
|
2737
|
+
displayName: "RCS",
|
|
2738
|
+
addressSpace: "rcs",
|
|
2739
|
+
surfaces: ["direct"],
|
|
2740
|
+
bindingCapabilities: ["rcs.message"],
|
|
2741
|
+
baseline: baseline("rcs", {
|
|
2742
|
+
operations: [...MESSAGE_CORE, "send.interactive", "read.receipt", "typing.indicate", "delete.message"],
|
|
2743
|
+
direction: { inbound: true, outbound: true },
|
|
2744
|
+
initiation: { agentInitiated: "opt_in_required", userInitiated: true, firstContactRule: "agent-initiated only with consent per AUP; STOP / UNSUBSCRIBE brand-wide" },
|
|
2745
|
+
windows: [],
|
|
2746
|
+
templates: { required: "never", reviewed: false },
|
|
2747
|
+
surfaces: ["direct"],
|
|
2748
|
+
messageTypes: ["text", "image", "audio", "video", "document", "card", "carousel", "interactive"],
|
|
2749
|
+
limits: { textChars: 3072, mediaBytes: 104857600 },
|
|
2750
|
+
receipts: ["delivered", "read"],
|
|
2751
|
+
edits: { edit: false, delete: true, reactions: false, commands: false },
|
|
2752
|
+
transport: ["webhook"],
|
|
2753
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2754
|
+
registration: [{ kind: "agent_launch", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"] }],
|
|
2755
|
+
readinessGates: [
|
|
2756
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2757
|
+
{ condition: "RegistrationApproved", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
|
|
2758
|
+
{ condition: "PeerReachable", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
|
|
2759
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
|
|
2760
|
+
]
|
|
2761
|
+
})
|
|
2762
|
+
}
|
|
2763
|
+
];
|
|
2764
|
+
new Map(CHANNELS.map((c) => [c.id, c]));
|
|
2765
|
+
CHANNEL_IDS = new Set(CHANNELS.map((c) => c.id));
|
|
2766
|
+
}
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
// ../contracts/src/channel-connector/operations.ts
|
|
2770
|
+
var OPERATION_MODES, OPERATIONS, OPERATION_IDS;
|
|
2771
|
+
var init_operations = __esm({
|
|
2772
|
+
"../contracts/src/channel-connector/operations.ts"() {
|
|
2773
|
+
OPERATION_MODES = ["message", "realtime"];
|
|
2774
|
+
OPERATIONS = [
|
|
2775
|
+
{ id: "receive.message", mode: "message", summary: "accept an inbound message" },
|
|
2776
|
+
{ id: "send.text", mode: "message", summary: "send a text message" },
|
|
2777
|
+
{ id: "send.media", mode: "message", summary: "send a message carrying a content item (image, audio, video, document)" },
|
|
2778
|
+
{ id: "send.template", mode: "message", summary: "send a reviewed template" },
|
|
2779
|
+
{ id: "send.interactive", mode: "message", summary: "send structured/interactive content" },
|
|
2780
|
+
{ id: "send.reaction", mode: "message", summary: "react to a message" },
|
|
2781
|
+
{ id: "edit.message", mode: "message", summary: "edit a sent message" },
|
|
2782
|
+
{ id: "delete.message", mode: "message", summary: "delete a sent message" },
|
|
2783
|
+
{ id: "initiate.conversation", mode: "message", summary: "first contact / outside any window" },
|
|
2784
|
+
{ id: "reply.in_window", mode: "message", summary: "reply inside an open window" },
|
|
2785
|
+
{ id: "call.receive", mode: "realtime", summary: "accept an inbound realtime session" },
|
|
2786
|
+
{ id: "call.place", mode: "realtime", summary: "place an outbound realtime session" },
|
|
2787
|
+
{ id: "read.receipt", mode: "message", summary: "mark a message as read" },
|
|
2788
|
+
{ id: "typing.indicate", mode: "message", summary: "typing indicator" },
|
|
2789
|
+
{ id: "command.register", mode: "message", summary: "register slash/bot commands" },
|
|
2790
|
+
{ id: "thread.reply", mode: "message", summary: "reply inside a thread" },
|
|
2791
|
+
{ id: "group.post", mode: "message", summary: "post to a group surface" },
|
|
2792
|
+
{ id: "channel.post", mode: "message", summary: "post to a channel surface" }
|
|
2793
|
+
];
|
|
2794
|
+
new Map(
|
|
2795
|
+
OPERATIONS.map((op) => [op.id, op])
|
|
2796
|
+
);
|
|
2797
|
+
OPERATION_IDS = new Set(OPERATIONS.map((op) => op.id));
|
|
2798
|
+
}
|
|
2799
|
+
});
|
|
2800
|
+
|
|
2801
|
+
// ../contracts/src/channel-connector/ids.ts
|
|
2802
|
+
var A2_SHIPPED_ROOTS, A2_PLATFORM_ROOTS, NAMESPACE_RE, SEMVER_RE;
|
|
2803
|
+
var init_ids2 = __esm({
|
|
2804
|
+
"../contracts/src/channel-connector/ids.ts"() {
|
|
2805
|
+
init_channels();
|
|
2806
|
+
init_operations();
|
|
2807
|
+
A2_SHIPPED_ROOTS = [
|
|
2808
|
+
"llm",
|
|
2809
|
+
"conversation",
|
|
2810
|
+
"channels",
|
|
2811
|
+
"tools",
|
|
2812
|
+
"guard",
|
|
2813
|
+
"store",
|
|
2814
|
+
"compliance",
|
|
2815
|
+
"carriers",
|
|
2816
|
+
"agents",
|
|
2817
|
+
"agentRun",
|
|
2818
|
+
"telemetry"
|
|
2819
|
+
];
|
|
2820
|
+
A2_PLATFORM_ROOTS = [
|
|
2821
|
+
...A2_SHIPPED_ROOTS,
|
|
2822
|
+
"policy",
|
|
2823
|
+
"identity",
|
|
2824
|
+
"binding",
|
|
2825
|
+
"provider",
|
|
2826
|
+
"workflow",
|
|
2827
|
+
"session",
|
|
2828
|
+
"engine",
|
|
2829
|
+
"message",
|
|
2830
|
+
"connector",
|
|
2831
|
+
"communication",
|
|
2832
|
+
"event",
|
|
2833
|
+
"reach",
|
|
2834
|
+
"handler",
|
|
2835
|
+
"plugin",
|
|
2836
|
+
"scope",
|
|
2837
|
+
"snapshot"
|
|
2838
|
+
];
|
|
2839
|
+
/* @__PURE__ */ new Set([
|
|
2840
|
+
...A2_PLATFORM_ROOTS,
|
|
2841
|
+
...[...OPERATION_IDS].map((id) => id.split(".")[0]),
|
|
2842
|
+
...CHANNEL_IDS
|
|
2843
|
+
]);
|
|
2844
|
+
NAMESPACE_RE = /^[a-z0-9]+\.[a-z0-9]+$/;
|
|
2845
|
+
SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
2846
|
+
}
|
|
2847
|
+
});
|
|
2848
|
+
function parseContentRef(ref) {
|
|
2849
|
+
const m = CONTENT_REF_RE.exec(ref);
|
|
2850
|
+
return m ? { principalId: m[1], contentId: m[2] } : null;
|
|
2851
|
+
}
|
|
2852
|
+
function isContentRef(value) {
|
|
2853
|
+
return typeof value === "string" && CONTENT_REF_RE.test(value);
|
|
2854
|
+
}
|
|
2855
|
+
function looksLikeProviderLocator(value) {
|
|
2856
|
+
return /^(https?:\/\/|s3:\/\/|gs:\/\/)/i.test(value) || value.includes("?") || value.includes("://");
|
|
2857
|
+
}
|
|
2858
|
+
var MODALITY_KINDS, STRUCTURED_KINDS, CONTENT_KINDS, LOSSINESS, CONTENT_REF_RE, MessageContentProvenance, noLocator, MessageContentMetadata, contentRefSchema, OriginalMessageContent, DerivedMessageContent;
|
|
2859
|
+
var init_content_kinds = __esm({
|
|
2860
|
+
"../contracts/src/channel-connector/content-kinds.ts"() {
|
|
2861
|
+
MODALITY_KINDS = ["text", "image", "audio", "video", "document"];
|
|
2862
|
+
STRUCTURED_KINDS = [
|
|
2863
|
+
"location",
|
|
2864
|
+
"contact",
|
|
2865
|
+
"sticker",
|
|
2866
|
+
"reaction",
|
|
2867
|
+
"interactive",
|
|
2868
|
+
"card",
|
|
2869
|
+
"carousel",
|
|
2870
|
+
"poll",
|
|
2871
|
+
"template"
|
|
2872
|
+
];
|
|
2873
|
+
CONTENT_KINDS = [...MODALITY_KINDS, ...STRUCTURED_KINDS];
|
|
2874
|
+
new Set(CONTENT_KINDS);
|
|
2875
|
+
LOSSINESS = ["lossless", "lossy", "semantic"];
|
|
2876
|
+
CONTENT_REF_RE = /^content:([A-Za-z0-9_-]+)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;
|
|
2877
|
+
MessageContentProvenance = z.object({
|
|
2878
|
+
/** The connector that produced the item (A7 id). */
|
|
2879
|
+
connector: z.string(),
|
|
2880
|
+
/** The provider-native identifier — reconciliation only; never agent-facing. */
|
|
2881
|
+
providerRef: z.string().optional(),
|
|
2882
|
+
receivedAt: z.string().datetime().optional()
|
|
2883
|
+
}).strict();
|
|
2884
|
+
noLocator = (label) => z.string().refine((v) => !looksLikeProviderLocator(v), { message: `${label} must not carry a provider locator \u2014 provider ids live in provenance.providerRef only` });
|
|
2885
|
+
MessageContentMetadata = z.object({
|
|
2886
|
+
bytes: z.number().int().nonnegative().optional(),
|
|
2887
|
+
durationMs: z.number().int().nonnegative().optional(),
|
|
2888
|
+
width: z.number().int().positive().optional(),
|
|
2889
|
+
height: z.number().int().positive().optional(),
|
|
2890
|
+
filename: noLocator("metadata.filename").optional(),
|
|
2891
|
+
caption: noLocator("metadata.caption").optional(),
|
|
2892
|
+
/** e.g. `voice_note` for audio. */
|
|
2893
|
+
variant: z.string().regex(/^[a-z0-9_]+$/).optional(),
|
|
2894
|
+
language: z.string().max(16).optional()
|
|
2895
|
+
}).strict();
|
|
2896
|
+
contentRefSchema = z.string().refine(isContentRef, {
|
|
2897
|
+
message: "ref must be an opaque platform content reference `content:<principal>/<uuid>`"
|
|
2898
|
+
});
|
|
2899
|
+
OriginalMessageContent = z.object({
|
|
2900
|
+
kind: z.enum(CONTENT_KINDS),
|
|
2901
|
+
/** MIME type or registry format id. */
|
|
2902
|
+
format: z.string().min(1),
|
|
2903
|
+
ref: contentRefSchema,
|
|
2904
|
+
metadata: MessageContentMetadata.default({}),
|
|
2905
|
+
provenance: MessageContentProvenance,
|
|
2906
|
+
/** Inline text for `text` kind only (the ref still names the stored item). */
|
|
2907
|
+
text: z.string().optional()
|
|
2908
|
+
}).strict().refine((item) => item.text === void 0 || item.kind === "text", { message: "inline text is only valid on kind=text" });
|
|
2909
|
+
DerivedMessageContent = OriginalMessageContent.innerType().extend({
|
|
2910
|
+
derivedFrom: contentRefSchema,
|
|
2911
|
+
/** The transform capability id (registry or extension id). */
|
|
2912
|
+
transform: z.string().min(1),
|
|
2913
|
+
/** A7 actor ref of the producer. */
|
|
2914
|
+
producer: z.string().min(1),
|
|
2915
|
+
lossiness: z.enum(LOSSINESS)
|
|
2916
|
+
}).strict().refine((item) => item.text === void 0 || item.kind === "text", { message: "inline text is only valid on kind=text" }).refine((item) => parseContentRef(item.derivedFrom)?.principalId === parseContentRef(item.ref)?.principalId, {
|
|
2917
|
+
message: "a derived item must derive from content of the same principal"
|
|
2918
|
+
});
|
|
2919
|
+
z.union([DerivedMessageContent, OriginalMessageContent]);
|
|
2920
|
+
}
|
|
2921
|
+
});
|
|
2922
|
+
var STEP_KINDS, OWNERSHIP_MODES, WorkflowStep, ConnectorWorkflow;
|
|
2923
|
+
var init_onboarding = __esm({
|
|
2924
|
+
"../contracts/src/channel-connector/onboarding.ts"() {
|
|
2925
|
+
STEP_KINDS = [
|
|
2926
|
+
"FORM",
|
|
2927
|
+
"SECRET",
|
|
2928
|
+
"OAUTH",
|
|
2929
|
+
"APP_INSTALL",
|
|
2930
|
+
"RESOURCE_SELECT",
|
|
2931
|
+
"RESOURCE_CREATE",
|
|
2932
|
+
"REGISTER",
|
|
2933
|
+
"SUBMIT_APPROVAL",
|
|
2934
|
+
"WAIT",
|
|
2935
|
+
"ACTION_REQUIRED",
|
|
2936
|
+
"GATEWAY_CONNECT",
|
|
2937
|
+
"PROBE",
|
|
2938
|
+
"ACTIVATE"
|
|
2939
|
+
];
|
|
2940
|
+
OWNERSHIP_MODES = ["platform_managed", "customer_owned"];
|
|
2941
|
+
new Set(OWNERSHIP_MODES);
|
|
2942
|
+
new Set(STEP_KINDS);
|
|
2943
|
+
WorkflowStep = z.object({
|
|
2944
|
+
id: z.string().min(1),
|
|
2945
|
+
kind: z.enum(STEP_KINDS),
|
|
2946
|
+
title: z.string().min(1),
|
|
2947
|
+
/** JSON Schema for FORM/SECRET inputs — schema-driven, no connector UI code. */
|
|
2948
|
+
schema: z.record(z.unknown()).optional(),
|
|
2949
|
+
/** Credential role for SECRET/OAUTH outputs. */
|
|
2950
|
+
role: z.string().optional(),
|
|
2951
|
+
approvalKind: z.string().optional(),
|
|
2952
|
+
resourceKind: z.string().optional(),
|
|
2953
|
+
mode: z.string().optional(),
|
|
2954
|
+
dependsOn: z.array(z.string()).default([]),
|
|
2955
|
+
optional: z.boolean().default(false),
|
|
2956
|
+
/**
|
|
2957
|
+
* Ownership modes this step applies to (absent = all). A REQUIRED step out of the run's mode is ownership-skipped
|
|
2958
|
+
* (`OUT_OF_MODE`), which is distinct from `optional` — the engine must skip it regardless of `optional` (#306 §9).
|
|
2959
|
+
*/
|
|
2960
|
+
ownership: z.array(z.enum(OWNERSHIP_MODES)).optional(),
|
|
2961
|
+
repeatable: z.boolean().default(false),
|
|
2962
|
+
producesChannels: z.array(z.string()).default([])
|
|
2963
|
+
}).strict();
|
|
2964
|
+
ConnectorWorkflow = z.object({
|
|
2965
|
+
version: z.string().min(1),
|
|
2966
|
+
steps: z.array(WorkflowStep).min(1),
|
|
2967
|
+
/** What ACTIVATE requires (condition type ids). */
|
|
2968
|
+
activationGates: z.array(z.string()),
|
|
2969
|
+
/** The single step that re-establishes credentials. */
|
|
2970
|
+
reauth: z.object({ stepId: z.string() }).strict(),
|
|
2971
|
+
/** Steps that adopt existing resources on transfer / reconnect. */
|
|
2972
|
+
relink: z.object({ stepIds: z.array(z.string()) }).strict()
|
|
2973
|
+
}).strict();
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
var EXCLUSIVITY, TIMEOUT_EFFECTS, RECONCILE_BY, ABSENCE_PROOF_METHODS, DUPLICATE_COSTS, RESOURCE_KIND_RE, ResourceKindDeclaration;
|
|
2977
|
+
var init_external_resources = __esm({
|
|
2978
|
+
"../contracts/src/channel-connector/external-resources.ts"() {
|
|
2979
|
+
init_ids2();
|
|
2980
|
+
EXCLUSIVITY = ["exclusive", "per_connection", "shared"];
|
|
2981
|
+
TIMEOUT_EFFECTS = ["UNKNOWN", "NO_EFFECT", "PROVIDER_ACCEPTS", "PROVIDER_REJECTS"];
|
|
2982
|
+
RECONCILE_BY = ["provider_ref", "correlation_tag", "listing", "none"];
|
|
2983
|
+
ABSENCE_PROOF_METHODS = ["provider_ref", "correlation_tag", "listing"];
|
|
2984
|
+
DUPLICATE_COSTS = ["low", "high"];
|
|
2985
|
+
RESOURCE_KIND_RE = /^[a-z0-9]+(\.[a-z0-9_]+)+$/;
|
|
2986
|
+
ResourceKindDeclaration = z.object({
|
|
2987
|
+
kind: z.string().regex(RESOURCE_KIND_RE, "resource kind must be a namespaced dotted id"),
|
|
2988
|
+
/** Customer-facing name of the kind (presentation only; the connector owns its provider vocabulary). */
|
|
2989
|
+
label: z.string().min(1).optional(),
|
|
2990
|
+
parentKinds: z.array(z.string()).default([]),
|
|
2991
|
+
/** Channels this kind enables (registry ids), if any. */
|
|
2992
|
+
channels: z.array(z.string()).default([]),
|
|
2993
|
+
/** An installation target (guild, workspace, tenant, business account). */
|
|
2994
|
+
isContainer: z.boolean().default(false),
|
|
2995
|
+
/** A channel identity (sender). */
|
|
2996
|
+
isSender: z.boolean().default(false),
|
|
2997
|
+
isRegulatory: z.boolean().default(false),
|
|
2998
|
+
exclusivity: z.enum(EXCLUSIVITY),
|
|
2999
|
+
/** Provider-side sharing cap, manifest DATA never a core constant (e.g. Meta WABA = 2). */
|
|
3000
|
+
maxHolders: z.number().int().positive().optional(),
|
|
3001
|
+
/** doc 06 `reconcile { by, absenceProof?, duplicateCost }`. */
|
|
3002
|
+
reconcile: z.object({
|
|
3003
|
+
by: z.enum(RECONCILE_BY),
|
|
3004
|
+
absenceProof: z.object({ minAgeSec: z.number().int().nonnegative(), method: z.enum(ABSENCE_PROOF_METHODS) }).strict().optional(),
|
|
3005
|
+
duplicateCost: z.enum(DUPLICATE_COSTS)
|
|
3006
|
+
}).strict(),
|
|
3007
|
+
/** doc 06 `release { destructive }`. */
|
|
3008
|
+
release: z.object({ destructive: z.boolean() }).strict().default({ destructive: false }),
|
|
3009
|
+
/** Timeout semantics for the effect that creates this kind (D-23). */
|
|
3010
|
+
createTimeoutEffect: z.enum(TIMEOUT_EFFECTS).default("UNKNOWN")
|
|
3011
|
+
}).strict().superRefine((decl, ctx) => {
|
|
3012
|
+
if (decl.isContainer && decl.exclusivity === "exclusive") {
|
|
3013
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `container kind ${decl.kind} cannot be exclusive` });
|
|
3014
|
+
}
|
|
3015
|
+
if (decl.maxHolders !== void 0 && decl.exclusivity === "exclusive" && decl.maxHolders !== 1) {
|
|
3016
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `exclusive kind ${decl.kind} cannot declare maxHolders > 1` });
|
|
3017
|
+
}
|
|
3018
|
+
if (decl.exclusivity === "shared" && (decl.isSender || decl.isContainer)) {
|
|
3019
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `shared kind ${decl.kind} is referenced by value and cannot be a sender or container` });
|
|
3020
|
+
}
|
|
3021
|
+
if (decl.reconcile.absenceProof && decl.reconcile.by === "none") {
|
|
3022
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `kind ${decl.kind}: absenceProof needs a reconcile method other than none` });
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
});
|
|
3027
|
+
|
|
3028
|
+
// ../contracts/src/channel-connector/approvals.ts
|
|
3029
|
+
var APPROVAL_KINDS;
|
|
3030
|
+
var init_approvals = __esm({
|
|
3031
|
+
"../contracts/src/channel-connector/approvals.ts"() {
|
|
3032
|
+
APPROVAL_KINDS = [
|
|
3033
|
+
"business_verification",
|
|
3034
|
+
"campaign_vetting",
|
|
3035
|
+
"toll_free_verification",
|
|
3036
|
+
"app_review",
|
|
3037
|
+
"privileged_permission_review",
|
|
3038
|
+
"admin_consent",
|
|
3039
|
+
"display_name_review",
|
|
3040
|
+
"template_review",
|
|
3041
|
+
"agent_launch",
|
|
3042
|
+
"brand_verification"
|
|
3043
|
+
];
|
|
3044
|
+
new Set(APPROVAL_KINDS);
|
|
3045
|
+
}
|
|
3046
|
+
});
|
|
3047
|
+
var DEPLOYMENT_MODELS, CREDENTIAL_STRENGTHS, CREDENTIAL_ROLES, INSTALLATION_KINDS, AUTHORIZATION_METHODS, AUTHORIZATION_CAPABILITIES, AuthorizationMethodDeclaration, INBOUND_AUTH_AVAILABILITY, InboundAuthMethodDeclaration, MutationApprovalDeclaration, ReadinessGateSchema, FormatsSchema, LimitsSchema, DescriptorOverride, DecisionRequestDeclaration;
|
|
3048
|
+
var init_manifest = __esm({
|
|
3049
|
+
"../contracts/src/channel-connector/manifest.ts"() {
|
|
3050
|
+
init_identity();
|
|
3051
|
+
init_approvals();
|
|
3052
|
+
init_capability_state();
|
|
3053
|
+
init_channels();
|
|
3054
|
+
init_content_kinds();
|
|
3055
|
+
init_external_resources();
|
|
3056
|
+
init_ids2();
|
|
3057
|
+
init_onboarding();
|
|
3058
|
+
init_operations();
|
|
3059
|
+
init_onboarding();
|
|
3060
|
+
DEPLOYMENT_MODELS = ["per_tenant_app", "platform_app", "both"];
|
|
3061
|
+
CREDENTIAL_STRENGTHS = ["ephemeral", "brokered", "exported_static"];
|
|
3062
|
+
CREDENTIAL_ROLES = ["primary", "signing", "webhook", "refresh", "app_signing", "oauth_client", "partner"];
|
|
3063
|
+
INSTALLATION_KINDS = ["guild", "user", "workspace", "org", "tenant", "team", "business_account", "chat"];
|
|
3064
|
+
new Set(INSTALLATION_KINDS);
|
|
3065
|
+
z.string().regex(/^cred:[a-z0-9_-]+\/[^\s]+$/, "A5 credential ref `cred:<store>/<path>`");
|
|
3066
|
+
AUTHORIZATION_METHODS = ["oauth", "api_key", "api_secret", "account_token", "bot_token", "delegated_app", "installation_token", "certificate", "other"];
|
|
3067
|
+
AUTHORIZATION_CAPABILITIES = ["discover", "provision", "send", "receive", "manage_resources", "manage_compliance", "manage_webhooks", "manage_installations", "manage_accounts"];
|
|
3068
|
+
AuthorizationMethodDeclaration = z.object({
|
|
3069
|
+
id: z.string().min(1),
|
|
3070
|
+
method: z.enum(AUTHORIZATION_METHODS),
|
|
3071
|
+
credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).min(1),
|
|
3072
|
+
capabilities: z.array(z.enum(AUTHORIZATION_CAPABILITIES)).default([]),
|
|
3073
|
+
scope: z.enum(["account", "container"]).optional()
|
|
3074
|
+
}).strict();
|
|
3075
|
+
INBOUND_AUTH_AVAILABILITY = ["ga", "beta", "private_beta", "unavailable"];
|
|
3076
|
+
InboundAuthMethodDeclaration = z.object({
|
|
3077
|
+
id: z.string().min(1),
|
|
3078
|
+
verifier: z.string().min(1),
|
|
3079
|
+
credentialRole: z.enum(CREDENTIAL_ROLES),
|
|
3080
|
+
compatibleWith: z.array(z.object({ authMethodId: z.string().min(1), reusesAuthCredential: z.boolean() }).strict()).default([]),
|
|
3081
|
+
availability: z.enum(INBOUND_AUTH_AVAILABILITY).default("ga")
|
|
3082
|
+
}).strict();
|
|
3083
|
+
MutationApprovalDeclaration = z.object({
|
|
3084
|
+
effect: z.string().min(1),
|
|
3085
|
+
approval: z.enum(["explicit", "implicit"]),
|
|
3086
|
+
capturePriorState: z.boolean().default(false)
|
|
3087
|
+
}).strict();
|
|
3088
|
+
ReadinessGateSchema = z.object({
|
|
3089
|
+
condition: z.string().min(1),
|
|
3090
|
+
class: z.enum(CONDITION_CLASSES),
|
|
3091
|
+
blocks: z.union([z.literal("*"), z.array(z.string())]),
|
|
3092
|
+
ttlSec: z.number().int().nonnegative(),
|
|
3093
|
+
evidence: z.enum(["configuration", "runtime"]).optional()
|
|
3094
|
+
}).strict();
|
|
3095
|
+
FormatsSchema = z.record(
|
|
3096
|
+
z.enum(CONTENT_KINDS),
|
|
3097
|
+
z.object({ mime: z.array(z.string()).optional(), variants: z.array(z.string()).optional() }).strict()
|
|
3098
|
+
);
|
|
3099
|
+
LimitsSchema = z.object({
|
|
3100
|
+
textChars: z.number().int().positive().optional(),
|
|
3101
|
+
textBytes: z.number().int().positive().optional(),
|
|
3102
|
+
mediaBytes: z.number().int().positive().optional(),
|
|
3103
|
+
segments: z.boolean().optional(),
|
|
3104
|
+
perSecond: z.number().positive().optional(),
|
|
3105
|
+
perRecipientPerSecond: z.number().positive().optional(),
|
|
3106
|
+
formats: FormatsSchema.optional(),
|
|
3107
|
+
note: z.string().optional()
|
|
3108
|
+
}).strict();
|
|
3109
|
+
DescriptorOverride = z.object({
|
|
3110
|
+
version: z.string().min(1).optional(),
|
|
3111
|
+
operations: z.array(z.string()).optional(),
|
|
3112
|
+
direction: z.object({ inbound: z.boolean(), outbound: z.boolean() }).strict().optional(),
|
|
3113
|
+
initiation: z.object({ agentInitiated: z.enum(AGENT_INITIATION), userInitiated: z.boolean(), firstContactRule: z.string() }).strict().optional(),
|
|
3114
|
+
windows: z.array(
|
|
3115
|
+
z.object({
|
|
3116
|
+
kind: z.literal("reply"),
|
|
3117
|
+
durationSec: z.number().int().positive(),
|
|
3118
|
+
opensOn: z.enum(["inbound_message", "user_action"]),
|
|
3119
|
+
outside: z.enum(WINDOW_OUTSIDE)
|
|
3120
|
+
}).strict()
|
|
3121
|
+
).optional(),
|
|
3122
|
+
templates: z.object({ required: z.enum(TEMPLATE_REQUIREMENT), reviewed: z.boolean(), categories: z.array(z.string()).optional() }).strict().optional(),
|
|
3123
|
+
surfaces: z.array(z.enum(SURFACES)).optional(),
|
|
3124
|
+
messageTypes: z.array(z.enum(CONTENT_KINDS)).optional(),
|
|
3125
|
+
limits: LimitsSchema.optional(),
|
|
3126
|
+
receipts: z.array(z.enum(RECEIPT_KINDS)).optional(),
|
|
3127
|
+
edits: z.object({ edit: z.boolean(), delete: z.boolean(), reactions: z.boolean(), commands: z.boolean() }).strict().optional(),
|
|
3128
|
+
transport: z.array(z.enum(TRANSPORT_KINDS)).optional(),
|
|
3129
|
+
identity: z.object({ peerIdKind: z.string(), peerIdScope: z.enum(PEER_ID_SCOPES) }).strict().optional(),
|
|
3130
|
+
requiredPermissions: z.array(z.string()).optional(),
|
|
3131
|
+
registration: z.array(z.object({ kind: z.string(), blocks: z.array(z.string()) }).strict()).optional(),
|
|
3132
|
+
readinessGates: z.array(ReadinessGateSchema).optional()
|
|
3133
|
+
}).strict();
|
|
3134
|
+
DecisionRequestDeclaration = z.object({
|
|
3135
|
+
kind: z.string().min(1),
|
|
3136
|
+
responseDeadlineSec: z.number().int().positive(),
|
|
3137
|
+
/** The provider's law when we stay silent. */
|
|
3138
|
+
defaultOutcome: z.enum(TIMEOUT_EFFECTS)
|
|
3139
|
+
}).strict();
|
|
3140
|
+
z.object({
|
|
3141
|
+
/** A7 `<publisher>/<name>`. */
|
|
3142
|
+
id: z.string().min(3),
|
|
3143
|
+
/** A7: semver. */
|
|
3144
|
+
version: z.string().regex(SEMVER_RE, "A7: plugin versions are semver"),
|
|
3145
|
+
displayName: z.string().min(1),
|
|
3146
|
+
publisher: z.string().min(1),
|
|
3147
|
+
/** A STABLE https URL of the provider's own console (the `provider_console` remediation carries it); absent ⇒ remediation has no link. */
|
|
3148
|
+
consoleUrl: z.string().url().optional(),
|
|
3149
|
+
/** The publisher's reverse-domain namespace (`com.acme`) under which every contributed extension id must sit; verified in ch. 05 (D-9). Absent for first-party connectors, which use registry ids only. */
|
|
3150
|
+
namespace: z.string().regex(NAMESPACE_RE, "namespace must be <root>.<label>").optional(),
|
|
3151
|
+
deployment: z.enum(DEPLOYMENT_MODELS),
|
|
3152
|
+
credentialStrength: z.enum(CREDENTIAL_STRENGTHS),
|
|
3153
|
+
/** Roles this connector's connections store refs under. */
|
|
3154
|
+
credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).default(["primary"]),
|
|
3155
|
+
/** Channels served, each with its descriptor diff over the baseline. */
|
|
3156
|
+
channels: z.array(z.object({ channel: z.string().min(1), descriptor: DescriptorOverride.default({}) }).strict()).min(1),
|
|
3157
|
+
/** Operations the connector implements (registry ids or its own extension ops). */
|
|
3158
|
+
operations: z.array(z.string()).min(1),
|
|
3159
|
+
/** Extension operations this connector defines — each MUST declare a mode. */
|
|
3160
|
+
extensionOperations: z.array(z.object({ id: z.string(), mode: z.enum(OPERATION_MODES), summary: z.string() }).strict()).default([]),
|
|
3161
|
+
onboarding: ConnectorWorkflow,
|
|
3162
|
+
resourceKinds: z.array(ResourceKindDeclaration).default([]),
|
|
3163
|
+
installationKinds: z.array(z.string()).default([]),
|
|
3164
|
+
transports: z.array(z.enum(TRANSPORT_KINDS)).min(1),
|
|
3165
|
+
/** doc 05 §2: transports the provider makes mutually exclusive (e.g. webhook vs long_poll). */
|
|
3166
|
+
transportsExclusive: z.array(z.array(z.enum(TRANSPORT_KINDS)).min(2)).default([]),
|
|
3167
|
+
/** Verifier id the ingress runs before parsing; required with the `webhook` transport. */
|
|
3168
|
+
webhookVerifier: z.string().min(1).optional(),
|
|
3169
|
+
/** Reconcile handler ids by resource kind (doc 02). */
|
|
3170
|
+
reconcilers: z.array(z.object({ resourceKind: z.string(), handler: z.string().min(1) }).strict()).default([]),
|
|
3171
|
+
/** Inbound decision requests with provider-defined defaults (D-23). */
|
|
3172
|
+
decisionRequests: z.array(DecisionRequestDeclaration).default([]),
|
|
3173
|
+
/** Identifier kinds contributed to the ch. 00 B-1 vocabulary. */
|
|
3174
|
+
identifierKinds: z.array(z.string()).default([]),
|
|
3175
|
+
/** Approval kinds used (registry or extension ids). */
|
|
3176
|
+
approvalKinds: z.array(z.string()).default([]),
|
|
3177
|
+
/** Attribution key names used under `platform_app` (doc 08 §4a step 3). */
|
|
3178
|
+
attributionKeys: z.array(z.string()).default([]),
|
|
3179
|
+
/** Quarantine window for PENDING_ATTRIBUTION (D-21), seconds. */
|
|
3180
|
+
pendingAttributionTtlSec: z.number().int().positive().default(7 * 86400),
|
|
3181
|
+
metadata: z.record(z.unknown()).default({}),
|
|
3182
|
+
/** Which relationship ownerships this connector serves (#306 §A/C1). A managed-capable connector MUST declare it explicitly; the default serves only the common OAuth-BYO connector. */
|
|
3183
|
+
ownershipModes: z.array(z.enum(OWNERSHIP_MODES)).min(1).default(["customer_owned"]),
|
|
3184
|
+
/** Authorization methods the connector supports (#306 §4/C3). */
|
|
3185
|
+
authorizationMethods: z.array(AuthorizationMethodDeclaration).default([]),
|
|
3186
|
+
/** Inbound-event authentication strategies (#306 §4/C4); `webhookVerifier` is the single-strategy shorthand, normalized by `resolveInboundAuthMethods`. */
|
|
3187
|
+
inboundAuthMethods: z.array(InboundAuthMethodDeclaration).default([]),
|
|
3188
|
+
/** Provider effects that require an explicit decision, keyed on stable effect ids (#306 L5/C5). */
|
|
3189
|
+
mutationApprovals: z.array(MutationApprovalDeclaration).default([])
|
|
3190
|
+
}).strict();
|
|
3191
|
+
}
|
|
3192
|
+
});
|
|
3193
|
+
|
|
3194
|
+
// ../contracts/src/channel-connector/index.ts
|
|
3195
|
+
var init_channel_connector = __esm({
|
|
3196
|
+
"../contracts/src/channel-connector/index.ts"() {
|
|
3197
|
+
init_ids2();
|
|
3198
|
+
init_operations();
|
|
3199
|
+
init_content_kinds();
|
|
3200
|
+
init_capability_state();
|
|
3201
|
+
init_onboarding();
|
|
3202
|
+
init_external_resources();
|
|
3203
|
+
init_approvals();
|
|
3204
|
+
init_channels();
|
|
3205
|
+
init_manifest();
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
function isSafeMonitoringHref(href) {
|
|
3209
|
+
if (href !== href.trim() || UNSAFE_CHARS.test(href) || href.includes("\\") || /%2e|%2f|%5c|%25/i.test(href)) return false;
|
|
3210
|
+
if (href.startsWith("/")) {
|
|
3211
|
+
if (href.startsWith("//")) return false;
|
|
3212
|
+
try {
|
|
3213
|
+
const u = new URL(href, "https://dashboard.invalid");
|
|
3214
|
+
return u.origin === "https://dashboard.invalid" && u.pathname.startsWith("/dashboard/") && u.pathname + u.search + u.hash === href;
|
|
3215
|
+
} catch {
|
|
3216
|
+
return false;
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
if (!href.startsWith("https://")) return false;
|
|
3220
|
+
try {
|
|
3221
|
+
const u = new URL(href);
|
|
3222
|
+
return u.protocol === "https:" && !u.username && !u.password && Boolean(u.hostname);
|
|
3223
|
+
} catch {
|
|
3224
|
+
return false;
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
var Label, Short, Body, Iso, MonitoringAuthor, MonitoringTone, UNSAFE_CHARS, MonitoringBlock, PresentedCommunication, MonitoringChannelId, MonitoringChannelRef, MonitoredPeer, MonitoredConversationSummary, MonitoredCommunication, MonitoringUnlinkedHistory, MONITORING_DIRECTIONS, MONITORING_STATES, MONITORING_MAX_ID_FILTERS, MONITORING_MAX_CHANNEL_FILTERS;
|
|
3228
|
+
var init_monitoring = __esm({
|
|
3229
|
+
"../contracts/src/monitoring.ts"() {
|
|
3230
|
+
Label = z.string().trim().min(1).max(60);
|
|
3231
|
+
Short = z.string().max(300);
|
|
3232
|
+
Body = z.string().max(4e3);
|
|
3233
|
+
Iso = z.string().datetime();
|
|
3234
|
+
MonitoringAuthor = z.enum(["peer", "agent", "system"]);
|
|
3235
|
+
MonitoringTone = z.enum(["ok", "warn", "bad", "info", "neutral"]);
|
|
3236
|
+
UNSAFE_CHARS = /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
|
|
3237
|
+
new RegExp(UNSAFE_CHARS.source, "gu");
|
|
3238
|
+
MonitoringBlock = z.discriminatedUnion("type", [
|
|
3239
|
+
/** One line of the conversation: what was said, by whom. */
|
|
3240
|
+
z.object({ type: z.literal("message"), author: MonitoringAuthor, text: Body, at: Iso.optional() }).strict(),
|
|
3241
|
+
/** Channel facts worth seeing at a glance (Discord server/channel, SMS from/to, call duration). */
|
|
3242
|
+
z.object({ type: z.literal("facts"), items: z.array(z.object({ label: Label, value: Short }).strict()).min(1).max(12) }).strict(),
|
|
3243
|
+
/** The communication's state in words, with a tone. */
|
|
3244
|
+
z.object({ type: z.literal("status"), label: Label, tone: MonitoringTone }).strict(),
|
|
3245
|
+
/** Somewhere to go: the provider's own view of it (https) or a deeper dashboard page. */
|
|
3246
|
+
z.object({ type: z.literal("link"), label: Label, href: z.string().max(2e3).refine(isSafeMonitoringHref, "href must be https or a /dashboard/ path") }).strict(),
|
|
3247
|
+
/** A dated lifecycle fact (sent, delivered, answered, ended). */
|
|
3248
|
+
z.object({ type: z.literal("event"), label: z.string().trim().min(1).max(120), at: Iso }).strict(),
|
|
3249
|
+
/** A spoken exchange, turn by turn. */
|
|
3250
|
+
z.object({
|
|
3251
|
+
type: z.literal("transcript"),
|
|
3252
|
+
turns: z.array(z.object({ author: MonitoringAuthor, text: Body, offsetMs: z.number().int().nonnegative().optional() }).strict()).max(500)
|
|
3253
|
+
}).strict()
|
|
3254
|
+
]);
|
|
3255
|
+
PresentedCommunication = z.object({
|
|
3256
|
+
/** One line for lists ("Asked: What is 2 + 2?"). */
|
|
3257
|
+
summary: z.string().max(140).optional(),
|
|
3258
|
+
/**
|
|
3259
|
+
* Something new starts at this communication — e.g. the agent began a fresh session with no memory of what came
|
|
3260
|
+
* before. The page draws a labelled divider before it. Additive to v1: a reader that ignores it loses nothing else.
|
|
3261
|
+
*/
|
|
3262
|
+
boundary: z.object({ label: z.string().trim().min(1).max(80) }).strict().optional(),
|
|
3263
|
+
blocks: z.array(MonitoringBlock).max(50)
|
|
3264
|
+
}).strict();
|
|
3265
|
+
MonitoringChannelId = z.string().regex(/^[a-z][a-z0-9_.-]{0,39}$/);
|
|
3266
|
+
MonitoringChannelRef = z.object({ id: z.string(), label: z.string() }).strict();
|
|
3267
|
+
MonitoredPeer = z.object({
|
|
3268
|
+
kind: z.string().nullable(),
|
|
3269
|
+
address: z.string().nullable(),
|
|
3270
|
+
/** From the channel's presenter (`describePeer`); null when it has no better name than the address. */
|
|
3271
|
+
displayName: z.string().nullable()
|
|
3272
|
+
}).strict();
|
|
3273
|
+
MonitoredConversationSummary = z.object({
|
|
3274
|
+
id: z.string().uuid(),
|
|
3275
|
+
status: z.string(),
|
|
3276
|
+
openedAt: z.string(),
|
|
3277
|
+
lastActivityAt: z.string(),
|
|
3278
|
+
agent: z.object({ id: z.string(), name: z.string() }).strict().nullable(),
|
|
3279
|
+
peer: MonitoredPeer,
|
|
3280
|
+
channels: z.array(MonitoringChannelRef),
|
|
3281
|
+
communications: z.number().int().nonnegative(),
|
|
3282
|
+
/** The latest INBOUND communication's one-liner (else the latest), as its channel presents it. */
|
|
3283
|
+
summary: z.string().nullable(),
|
|
3284
|
+
/** Something in it failed (a message not delivered, a call that failed). */
|
|
3285
|
+
failed: z.boolean(),
|
|
3286
|
+
/** A call in it is still in progress. */
|
|
3287
|
+
live: z.boolean()
|
|
3288
|
+
}).strict();
|
|
3289
|
+
MonitoredCommunication = z.object({
|
|
3290
|
+
id: z.string().uuid(),
|
|
3291
|
+
type: z.string(),
|
|
3292
|
+
channel: MonitoringChannelRef,
|
|
3293
|
+
direction: z.string(),
|
|
3294
|
+
status: z.string(),
|
|
3295
|
+
startedAt: z.string(),
|
|
3296
|
+
endedAt: z.string().nullable(),
|
|
3297
|
+
/** Which presenter shaped it (`vl/discord`, `vl/sms`, `generic`), for support. */
|
|
3298
|
+
presenter: z.string(),
|
|
3299
|
+
presented: PresentedCommunication
|
|
3300
|
+
}).strict();
|
|
3301
|
+
MonitoringUnlinkedHistory = z.object({ calls: z.boolean(), messages: z.boolean() }).strict();
|
|
3302
|
+
z.object({
|
|
3303
|
+
blocksVersion: z.number().int(),
|
|
3304
|
+
scope: z.string(),
|
|
3305
|
+
conversations: z.array(MonitoredConversationSummary),
|
|
3306
|
+
/** Opaque; pass back as `cursor` for the next (older) page. Null on the last page. */
|
|
3307
|
+
nextCursor: z.string().nullable(),
|
|
3308
|
+
/** History from before a channel was linked to the ledger (not shown here, 🔒 control-plane F8/F12). */
|
|
3309
|
+
unlinkedHistory: MonitoringUnlinkedHistory
|
|
3310
|
+
}).strict();
|
|
3311
|
+
z.object({
|
|
3312
|
+
blocksVersion: z.number().int(),
|
|
3313
|
+
scope: z.string(),
|
|
3314
|
+
conversation: MonitoredConversationSummary,
|
|
3315
|
+
/** Oldest first. The page holds the LATEST communications; `earlierCursor` fetches the ones before them. */
|
|
3316
|
+
communications: z.array(MonitoredCommunication),
|
|
3317
|
+
earlierCursor: z.string().nullable()
|
|
3318
|
+
}).strict();
|
|
3319
|
+
z.object({
|
|
3320
|
+
scope: z.string(),
|
|
3321
|
+
/** Channels a presenter currently shows, named by that presenter. */
|
|
3322
|
+
channels: z.array(MonitoringChannelRef),
|
|
3323
|
+
/** The Lines (numbers, bots) conversations in this scope arrived on. */
|
|
3324
|
+
lines: z.array(z.object({ id: z.string().uuid(), label: z.string() }).strict())
|
|
3325
|
+
}).strict();
|
|
3326
|
+
MONITORING_DIRECTIONS = ["inbound", "outbound"];
|
|
3327
|
+
MONITORING_STATES = ["live", "failed", "ok"];
|
|
3328
|
+
MONITORING_MAX_ID_FILTERS = 50;
|
|
3329
|
+
MONITORING_MAX_CHANNEL_FILTERS = 20;
|
|
3330
|
+
z.object({
|
|
3331
|
+
scope: z.enum(["env:dev", "env:stg", "env:prd"]).optional(),
|
|
3332
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
3333
|
+
cursor: z.string().max(200).optional(),
|
|
3334
|
+
from: z.string().datetime().optional(),
|
|
3335
|
+
to: z.string().datetime().optional(),
|
|
3336
|
+
channel: z.array(MonitoringChannelId).max(MONITORING_MAX_CHANNEL_FILTERS).optional(),
|
|
3337
|
+
direction: z.array(z.enum(MONITORING_DIRECTIONS)).max(2).optional(),
|
|
3338
|
+
line: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
|
|
3339
|
+
agent: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
|
|
3340
|
+
status: z.array(z.enum(MONITORING_STATES)).max(3).optional(),
|
|
3341
|
+
customer: z.string().trim().max(100).optional()
|
|
3342
|
+
}).strict();
|
|
3343
|
+
}
|
|
3344
|
+
});
|
|
3345
|
+
var WalletRefusalCode, WalletMode, WalletInvoiceStatus, WalletInvoice, WalletEntryKind, WalletEntry, TOPUP_MIN_USD, TOPUP_MAX_USD;
|
|
3346
|
+
var init_wallet = __esm({
|
|
3347
|
+
"../contracts/src/wallet.ts"() {
|
|
3348
|
+
WalletRefusalCode = z.enum([
|
|
3349
|
+
// Prepaid balance (or an invoiced credit limit) cannot cover the action.
|
|
3350
|
+
"insufficient_balance",
|
|
3351
|
+
// The wallet is frozen after a card dispute, until an operator unfreezes it.
|
|
3352
|
+
"account_frozen",
|
|
3353
|
+
// An invoice is unpaid past its 7 days' grace (B-19): new calls, messages and answers are refused; inbound calls are
|
|
3354
|
+
// still answered. Paying the invoice restores the workspace.
|
|
3355
|
+
"account_suspended",
|
|
3356
|
+
// The action's reservation already closed (e.g. a call ended or expired) — it cannot be re-admitted.
|
|
3357
|
+
"hold_closed"
|
|
3358
|
+
]);
|
|
3359
|
+
z.object({
|
|
3360
|
+
error: WalletRefusalCode,
|
|
3361
|
+
code: WalletRefusalCode,
|
|
3362
|
+
availableUsd: z.string(),
|
|
3363
|
+
needUsd: z.string(),
|
|
3364
|
+
message: z.string()
|
|
3365
|
+
});
|
|
3366
|
+
WalletMode = z.enum(["prepaid", "invoiced"]);
|
|
3367
|
+
z.object({
|
|
3368
|
+
mode: WalletMode,
|
|
3369
|
+
status: z.enum(["active", "frozen"]),
|
|
3370
|
+
// Decimal strings, six places (the ledger's micro-dollar precision).
|
|
3371
|
+
balanceUsd: z.string(),
|
|
3372
|
+
// Reserved for calls in progress.
|
|
3373
|
+
heldUsd: z.string(),
|
|
3374
|
+
// balance − held (+ credit limit when invoiced); null = unlimited.
|
|
3375
|
+
availableUsd: z.string().nullable(),
|
|
3376
|
+
creditLimitUsd: z.string().nullable(),
|
|
3377
|
+
// A card saved by the first top-up — what auto top-up charges.
|
|
3378
|
+
cardOnFile: z.boolean(),
|
|
3379
|
+
autoTopup: z.object({ enabled: z.boolean(), thresholdUsd: z.string(), amountUsd: z.string() }),
|
|
3380
|
+
// B-19: 'past_due' = an invoice is overdue (a pay-now banner); 'suspended' = paused for it (or by an operator).
|
|
3381
|
+
standing: z.enum(["good", "past_due", "suspended"]),
|
|
3382
|
+
// Why it is suspended: an invoice unpaid past grace (paying restores it) or an operator (only support lifts it).
|
|
3383
|
+
suspension: z.enum(["unpaid_invoice", "operator"]).nullable(),
|
|
3384
|
+
// The oldest overdue invoice, when there is one.
|
|
3385
|
+
overdueInvoice: z.object({ amountUsd: z.string(), dueAt: z.string(), payUrl: z.string().nullable() }).nullable(),
|
|
3386
|
+
// Invoiced wallets: what the next invoice holds so far (charges since the last invoice); null when prepaid.
|
|
3387
|
+
uninvoicedUsd: z.string().nullable()
|
|
3388
|
+
});
|
|
3389
|
+
WalletInvoiceStatus = z.enum(["draft", "open", "paid", "void", "uncollectible"]);
|
|
3390
|
+
WalletInvoice = z.object({
|
|
3391
|
+
id: z.string(),
|
|
3392
|
+
// 'final' = issued when the workspace moved to prepaid.
|
|
3393
|
+
kind: z.enum(["monthly", "final"]),
|
|
3394
|
+
periodStart: z.string(),
|
|
3395
|
+
periodEnd: z.string(),
|
|
3396
|
+
amountUsd: z.string(),
|
|
3397
|
+
status: WalletInvoiceStatus,
|
|
3398
|
+
issuedAt: z.string().nullable(),
|
|
3399
|
+
dueAt: z.string().nullable(),
|
|
3400
|
+
paidAt: z.string().nullable(),
|
|
3401
|
+
// Stripe's hosted page to view and pay it (null until issued).
|
|
3402
|
+
payUrl: z.string().nullable()
|
|
3403
|
+
});
|
|
3404
|
+
z.object({ invoices: z.array(WalletInvoice) });
|
|
3405
|
+
WalletEntryKind = z.enum(["grant", "topup", "usage", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
3406
|
+
WalletEntry = z.object({
|
|
3407
|
+
id: z.string(),
|
|
3408
|
+
kind: WalletEntryKind,
|
|
3409
|
+
// Signed: a credit is positive, a debit negative.
|
|
3410
|
+
amountUsd: z.string(),
|
|
3411
|
+
balanceAfterUsd: z.string(),
|
|
3412
|
+
// The usage row a debit charged (usage entries only).
|
|
3413
|
+
usageEventId: z.string().nullable(),
|
|
3414
|
+
note: z.string().nullable(),
|
|
3415
|
+
createdAt: z.string()
|
|
3416
|
+
});
|
|
3417
|
+
z.object({
|
|
3418
|
+
entries: z.array(WalletEntry),
|
|
3419
|
+
// Pass as `before` for the next (older) page; null when there is none.
|
|
3420
|
+
nextBefore: z.string().nullable()
|
|
3421
|
+
});
|
|
3422
|
+
TOPUP_MIN_USD = 10;
|
|
3423
|
+
TOPUP_MAX_USD = 500;
|
|
3424
|
+
z.object({ amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD) });
|
|
3425
|
+
z.object({
|
|
3426
|
+
enabled: z.boolean(),
|
|
3427
|
+
thresholdUsd: z.number().min(1).max(TOPUP_MAX_USD),
|
|
3428
|
+
amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD)
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
2105
3432
|
|
|
2106
3433
|
// ../contracts/src/index.ts
|
|
2107
3434
|
var init_src = __esm({
|
|
@@ -2120,6 +3447,8 @@ var init_src = __esm({
|
|
|
2120
3447
|
init_forms();
|
|
2121
3448
|
init_consultation();
|
|
2122
3449
|
init_agent_config();
|
|
3450
|
+
init_agent_versions();
|
|
3451
|
+
init_providers();
|
|
2123
3452
|
init_model_catalog();
|
|
2124
3453
|
init_evaluations();
|
|
2125
3454
|
init_tests();
|
|
@@ -2134,6 +3463,7 @@ var init_src = __esm({
|
|
|
2134
3463
|
init_flow_validate();
|
|
2135
3464
|
init_flow_builtins();
|
|
2136
3465
|
init_compliance();
|
|
3466
|
+
init_sms_keywords();
|
|
2137
3467
|
init_call_recording();
|
|
2138
3468
|
init_outbound();
|
|
2139
3469
|
init_connection();
|
|
@@ -2148,20 +3478,27 @@ var init_src = __esm({
|
|
|
2148
3478
|
init_playbooks();
|
|
2149
3479
|
init_playbook_versions();
|
|
2150
3480
|
init_playbook_compile();
|
|
3481
|
+
init_effect_stack();
|
|
3482
|
+
init_engine();
|
|
3483
|
+
init_identity();
|
|
3484
|
+
init_interaction();
|
|
3485
|
+
init_channel_connector();
|
|
3486
|
+
init_monitoring();
|
|
3487
|
+
init_wallet();
|
|
2151
3488
|
}
|
|
2152
3489
|
});
|
|
2153
3490
|
|
|
2154
3491
|
// src/runtime/graph/interpolate.ts
|
|
2155
|
-
function interpolate(template,
|
|
3492
|
+
function interpolate(template, scope2) {
|
|
2156
3493
|
if (!template || template.indexOf("{{") === -1) return template;
|
|
2157
3494
|
return template.replace(TOKEN, (_match, expr) => {
|
|
2158
|
-
const value = resolvePath(
|
|
3495
|
+
const value = resolvePath(scope2, expr.trim());
|
|
2159
3496
|
return value === void 0 || value === null ? "" : String(value);
|
|
2160
3497
|
});
|
|
2161
3498
|
}
|
|
2162
|
-
function resolvePath(
|
|
3499
|
+
function resolvePath(scope2, path) {
|
|
2163
3500
|
const parts = path.split(".").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
2164
|
-
let current =
|
|
3501
|
+
let current = scope2;
|
|
2165
3502
|
for (const part of parts) {
|
|
2166
3503
|
if (current === null || typeof current !== "object") return void 0;
|
|
2167
3504
|
const obj = current;
|
|
@@ -2211,6 +3548,20 @@ var init_builtins = __esm({
|
|
|
2211
3548
|
init_src();
|
|
2212
3549
|
}
|
|
2213
3550
|
});
|
|
3551
|
+
function createChatClient(config = {}) {
|
|
3552
|
+
return new OpenAI({
|
|
3553
|
+
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
3554
|
+
...config.baseURL ? { baseURL: config.baseURL } : {},
|
|
3555
|
+
...config.timeoutMs ? { timeout: config.timeoutMs } : {}
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
var init_dist = __esm({
|
|
3559
|
+
"../llm-client/dist/index.js"() {
|
|
3560
|
+
}
|
|
3561
|
+
});
|
|
3562
|
+
function runWithOpenAIScope(client, fn) {
|
|
3563
|
+
return scope.run(client, fn);
|
|
3564
|
+
}
|
|
2214
3565
|
function defaultOpenAIOptions() {
|
|
2215
3566
|
if (!credential) return {};
|
|
2216
3567
|
return {
|
|
@@ -2218,17 +3569,283 @@ function defaultOpenAIOptions() {
|
|
|
2218
3569
|
...credential.baseURL ? { baseURL: credential.baseURL } : {}
|
|
2219
3570
|
};
|
|
2220
3571
|
}
|
|
3572
|
+
function internalChatClient(opts) {
|
|
3573
|
+
const cred = defaultOpenAIOptions();
|
|
3574
|
+
return createChatClient({
|
|
3575
|
+
...cred.apiKey ? { apiKey: cred.apiKey } : {},
|
|
3576
|
+
...cred.baseURL ? { baseURL: cred.baseURL } : {},
|
|
3577
|
+
...{}
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
2221
3580
|
function defaultOpenAI() {
|
|
2222
|
-
return cached ??=
|
|
3581
|
+
return scope.getStore() ?? (cached ??= internalChatClient());
|
|
2223
3582
|
}
|
|
2224
3583
|
function hasOpenAiKey() {
|
|
2225
|
-
return credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
|
|
3584
|
+
return scope.getStore() !== void 0 || credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
|
|
2226
3585
|
}
|
|
2227
|
-
var credential, cached;
|
|
3586
|
+
var credential, cached, scope;
|
|
2228
3587
|
var init_openai_default = __esm({
|
|
2229
3588
|
"src/runtime/openai-default.ts"() {
|
|
3589
|
+
init_dist();
|
|
2230
3590
|
credential = null;
|
|
2231
3591
|
cached = null;
|
|
3592
|
+
scope = new AsyncLocalStorage();
|
|
3593
|
+
}
|
|
3594
|
+
});
|
|
3595
|
+
var init_ssrf = __esm({
|
|
3596
|
+
"src/brain/ssrf.ts"() {
|
|
3597
|
+
}
|
|
3598
|
+
});
|
|
3599
|
+
|
|
3600
|
+
// src/brain/serialize.ts
|
|
3601
|
+
var init_serialize = __esm({
|
|
3602
|
+
"src/brain/serialize.ts"() {
|
|
3603
|
+
}
|
|
3604
|
+
});
|
|
3605
|
+
|
|
3606
|
+
// src/brain/transport-callback.ts
|
|
3607
|
+
var init_transport_callback = __esm({
|
|
3608
|
+
"src/brain/transport-callback.ts"() {
|
|
3609
|
+
init_serialize();
|
|
3610
|
+
}
|
|
3611
|
+
});
|
|
3612
|
+
|
|
3613
|
+
// src/brain/connector-llm.ts
|
|
3614
|
+
var init_connector_llm = __esm({
|
|
3615
|
+
"src/brain/connector-llm.ts"() {
|
|
3616
|
+
init_serialize();
|
|
3617
|
+
}
|
|
3618
|
+
});
|
|
3619
|
+
var init_wrap = __esm({
|
|
3620
|
+
"src/providers/wrap.ts"() {
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
|
|
3624
|
+
// src/providers/index.ts
|
|
3625
|
+
async function importOptional(spec, hint) {
|
|
3626
|
+
try {
|
|
3627
|
+
return await import(spec);
|
|
3628
|
+
} catch {
|
|
3629
|
+
throw new Error(`${hint}: "${spec}" is not installed. Add it with: pnpm add ${spec}`);
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
function withCreds(opts, creds2) {
|
|
3633
|
+
if (creds2?.apiKey) opts["apiKey"] = creds2.apiKey;
|
|
3634
|
+
if (creds2?.baseURL) opts["baseURL"] = creds2.baseURL;
|
|
3635
|
+
return opts;
|
|
3636
|
+
}
|
|
3637
|
+
var deepgram, openai, anthropic, cartesia, elevenlabs, assemblyai, google, silero;
|
|
3638
|
+
var init_providers2 = __esm({
|
|
3639
|
+
"src/providers/index.ts"() {
|
|
3640
|
+
init_ssrf();
|
|
3641
|
+
init_transport_callback();
|
|
3642
|
+
init_connector_llm();
|
|
3643
|
+
init_wrap();
|
|
3644
|
+
deepgram = {
|
|
3645
|
+
stt(options = {}) {
|
|
3646
|
+
return async () => {
|
|
3647
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.stt");
|
|
3648
|
+
return new dg.STT(
|
|
3649
|
+
withCreds(
|
|
3650
|
+
{ model: options.model ?? "nova-3", language: options.language ?? "en-US" },
|
|
3651
|
+
options
|
|
3652
|
+
)
|
|
3653
|
+
);
|
|
3654
|
+
};
|
|
3655
|
+
},
|
|
3656
|
+
tts(options = {}) {
|
|
3657
|
+
return async () => {
|
|
3658
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.tts");
|
|
3659
|
+
return new dg.TTS(
|
|
3660
|
+
withCreds({ model: options.model ?? "aura-2-harmonia-en" }, options)
|
|
3661
|
+
);
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
openai = {
|
|
3666
|
+
llm(options = {}) {
|
|
3667
|
+
return async () => {
|
|
3668
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.llm");
|
|
3669
|
+
return new oa.LLM(
|
|
3670
|
+
withCreds({ model: options.model ?? "gpt-4o-mini" }, options)
|
|
3671
|
+
);
|
|
3672
|
+
};
|
|
3673
|
+
},
|
|
3674
|
+
tts(options = {}) {
|
|
3675
|
+
return async () => {
|
|
3676
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.tts");
|
|
3677
|
+
const opts = { model: options.model ?? "tts-1" };
|
|
3678
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3679
|
+
if (options.instructions) opts["instructions"] = options.instructions;
|
|
3680
|
+
return new oa.TTS(withCreds(opts, options));
|
|
3681
|
+
};
|
|
3682
|
+
},
|
|
3683
|
+
// Speech-to-speech via the OpenAI Realtime API. Returns a RealtimeProvider
|
|
3684
|
+
// that the SDK uses in place of the stt/llm/tts pipeline.
|
|
3685
|
+
realtime(options = {}) {
|
|
3686
|
+
return async () => {
|
|
3687
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.realtime");
|
|
3688
|
+
const opts = {};
|
|
3689
|
+
if (options.model) opts["model"] = options.model;
|
|
3690
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3691
|
+
return new oa.realtime.RealtimeModel(withCreds(opts, options));
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
anthropic = {
|
|
3696
|
+
llm(options = {}) {
|
|
3697
|
+
return async () => {
|
|
3698
|
+
const an = await importOptional("@livekit/agents-plugin-anthropic", "anthropic.llm");
|
|
3699
|
+
return new an.LLM(
|
|
3700
|
+
withCreds({ model: options.model ?? "claude-sonnet-4" }, options)
|
|
3701
|
+
);
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
};
|
|
3705
|
+
cartesia = {
|
|
3706
|
+
tts(options = {}) {
|
|
3707
|
+
return async () => {
|
|
3708
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.tts");
|
|
3709
|
+
return new ct.TTS(options);
|
|
3710
|
+
};
|
|
3711
|
+
},
|
|
3712
|
+
stt(options = {}) {
|
|
3713
|
+
return async () => {
|
|
3714
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.stt");
|
|
3715
|
+
const opts = {};
|
|
3716
|
+
if (options.model) opts["model"] = options.model;
|
|
3717
|
+
if (options.language) opts["language"] = options.language;
|
|
3718
|
+
return new ct.STT(withCreds(opts, options));
|
|
3719
|
+
};
|
|
3720
|
+
}
|
|
3721
|
+
};
|
|
3722
|
+
elevenlabs = {
|
|
3723
|
+
tts(options = {}) {
|
|
3724
|
+
return async () => {
|
|
3725
|
+
const el = await importOptional("@voicelayer/agents-plugin-elevenlabs", "elevenlabs.tts");
|
|
3726
|
+
const opts = {};
|
|
3727
|
+
if (options.voice) opts["voiceId"] = options.voice;
|
|
3728
|
+
if (options.model) opts["model"] = options.model;
|
|
3729
|
+
return new el.TTS(withCreds(opts, options));
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
};
|
|
3733
|
+
assemblyai = {
|
|
3734
|
+
stt(options = {}) {
|
|
3735
|
+
return async () => {
|
|
3736
|
+
const aai = await importOptional("@voicelayer/agents-plugin-assemblyai", "assemblyai.stt");
|
|
3737
|
+
const opts = {};
|
|
3738
|
+
if (options.language) opts["language"] = options.language;
|
|
3739
|
+
return new aai.STT(withCreds(opts, options));
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
google = {
|
|
3744
|
+
llm(options = {}) {
|
|
3745
|
+
return async () => {
|
|
3746
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.llm");
|
|
3747
|
+
return new g.LLM(
|
|
3748
|
+
withCreds({ model: options.model ?? "gemini-2.0-flash" }, options)
|
|
3749
|
+
);
|
|
3750
|
+
};
|
|
3751
|
+
},
|
|
3752
|
+
realtime(options = {}) {
|
|
3753
|
+
return async () => {
|
|
3754
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.realtime");
|
|
3755
|
+
const opts = {};
|
|
3756
|
+
if (options.model) opts["model"] = options.model;
|
|
3757
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3758
|
+
return new g.beta.realtime.RealtimeModel(withCreds(opts, options));
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
};
|
|
3762
|
+
silero = {
|
|
3763
|
+
vad() {
|
|
3764
|
+
return async () => {
|
|
3765
|
+
const sil = await importOptional("@voicelayer/agents-plugin-silero", "silero.vad");
|
|
3766
|
+
return await sil.VAD.load();
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
};
|
|
3770
|
+
}
|
|
3771
|
+
});
|
|
3772
|
+
|
|
3773
|
+
// src/providers/registry.ts
|
|
3774
|
+
function entry(id, bits) {
|
|
3775
|
+
const identity = providerIdentity(id);
|
|
3776
|
+
if (!identity) throw new Error(`PROVIDER_REGISTRY: no provider identity in contracts for '${id}'`);
|
|
3777
|
+
return { name: identity.id, capabilities: identity.capabilities, ...bits };
|
|
3778
|
+
}
|
|
3779
|
+
var model, lang, voice, creds;
|
|
3780
|
+
var init_registry = __esm({
|
|
3781
|
+
"src/providers/registry.ts"() {
|
|
3782
|
+
init_providers2();
|
|
3783
|
+
init_src();
|
|
3784
|
+
model = (o) => o.model ? { model: o.model } : {};
|
|
3785
|
+
lang = (o) => o.language ? { language: o.language } : {};
|
|
3786
|
+
voice = (o) => o.voice ? { voice: o.voice } : {};
|
|
3787
|
+
creds = (o) => o.creds ?? {};
|
|
3788
|
+
({
|
|
3789
|
+
deepgram: entry("deepgram", {
|
|
3790
|
+
failureClass: "vendor-api",
|
|
3791
|
+
engines: ["livekit"],
|
|
3792
|
+
stt: (o) => deepgram.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
3793
|
+
// Deepgram folds the voice into the model id (aura-2-<name>-en): wire config
|
|
3794
|
+
// passes a voice, code config passes a model — both land as `model`.
|
|
3795
|
+
tts: (o) => {
|
|
3796
|
+
const m = o.voice ?? o.model;
|
|
3797
|
+
return deepgram.tts({ ...m ? { model: m } : {}, ...creds(o) });
|
|
3798
|
+
}
|
|
3799
|
+
}),
|
|
3800
|
+
openai: entry("openai", {
|
|
3801
|
+
failureClass: "vendor-api",
|
|
3802
|
+
engines: ["livekit", "text"],
|
|
3803
|
+
llm: (o) => openai.llm({ ...model(o), ...creds(o) }),
|
|
3804
|
+
tts: (o) => openai.tts({ ...voice(o), ...model(o), ...creds(o) }),
|
|
3805
|
+
realtime: (o) => openai.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
3806
|
+
}),
|
|
3807
|
+
anthropic: entry("anthropic", {
|
|
3808
|
+
failureClass: "vendor-api",
|
|
3809
|
+
engines: ["livekit", "text"],
|
|
3810
|
+
llm: (o) => anthropic.llm({ ...model(o), ...creds(o) })
|
|
3811
|
+
}),
|
|
3812
|
+
google: entry("google", {
|
|
3813
|
+
failureClass: "vendor-api",
|
|
3814
|
+
engines: ["livekit", "text"],
|
|
3815
|
+
llm: (o) => google.llm({ ...model(o), ...creds(o) }),
|
|
3816
|
+
realtime: (o) => google.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
3817
|
+
}),
|
|
3818
|
+
cartesia: entry("cartesia", {
|
|
3819
|
+
failureClass: "vendor-api",
|
|
3820
|
+
engines: ["livekit"],
|
|
3821
|
+
stt: (o) => cartesia.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
3822
|
+
tts: (o) => cartesia.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
3823
|
+
}),
|
|
3824
|
+
elevenlabs: entry("elevenlabs", {
|
|
3825
|
+
failureClass: "vendor-api",
|
|
3826
|
+
engines: ["livekit"],
|
|
3827
|
+
tts: (o) => elevenlabs.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
3828
|
+
}),
|
|
3829
|
+
assemblyai: entry("assemblyai", {
|
|
3830
|
+
failureClass: "vendor-api",
|
|
3831
|
+
engines: ["livekit"],
|
|
3832
|
+
stt: (o) => assemblyai.stt({ ...lang(o), ...creds(o) })
|
|
3833
|
+
// no model knob
|
|
3834
|
+
}),
|
|
3835
|
+
silero: entry("silero", {
|
|
3836
|
+
failureClass: "local",
|
|
3837
|
+
engines: ["livekit"],
|
|
3838
|
+
vad: () => silero.vad()
|
|
3839
|
+
})
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
});
|
|
3843
|
+
|
|
3844
|
+
// src/providers/llm.ts
|
|
3845
|
+
var init_llm = __esm({
|
|
3846
|
+
"src/providers/llm.ts"() {
|
|
3847
|
+
init_openai_default();
|
|
3848
|
+
init_registry();
|
|
2232
3849
|
}
|
|
2233
3850
|
});
|
|
2234
3851
|
|
|
@@ -2241,6 +3858,7 @@ function dotProduct(a, b) {
|
|
|
2241
3858
|
}
|
|
2242
3859
|
function createOpenAIEmbedder() {
|
|
2243
3860
|
const cache = /* @__PURE__ */ new Map();
|
|
3861
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
2244
3862
|
const available = () => hasOpenAiKey();
|
|
2245
3863
|
function remember(key, vec) {
|
|
2246
3864
|
if (cache.size >= CACHE_MAX) {
|
|
@@ -2259,24 +3877,31 @@ function createOpenAIEmbedder() {
|
|
|
2259
3877
|
if (!available() || !key) return null;
|
|
2260
3878
|
const hit = cache.get(key);
|
|
2261
3879
|
if (hit) return hit;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
})
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
3880
|
+
const pending = inflight.get(key);
|
|
3881
|
+
if (pending) return pending;
|
|
3882
|
+
const request = (async () => {
|
|
3883
|
+
const controller = new AbortController();
|
|
3884
|
+
const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
|
|
3885
|
+
try {
|
|
3886
|
+
const res = await defaultOpenAI().embeddings.create(
|
|
3887
|
+
{ model: EMBED_MODEL, input: key },
|
|
3888
|
+
{ signal: controller.signal }
|
|
3889
|
+
);
|
|
3890
|
+
const vec = res.data[0]?.embedding;
|
|
3891
|
+
if (!Array.isArray(vec)) return null;
|
|
3892
|
+
return remember(key, vec);
|
|
3893
|
+
} catch (err) {
|
|
3894
|
+
graphWarn("embed failed; router will fall back to judge", {
|
|
3895
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3896
|
+
});
|
|
3897
|
+
return null;
|
|
3898
|
+
} finally {
|
|
3899
|
+
clearTimeout(timer);
|
|
3900
|
+
inflight.delete(key);
|
|
3901
|
+
}
|
|
3902
|
+
})();
|
|
3903
|
+
inflight.set(key, request);
|
|
3904
|
+
return request;
|
|
2280
3905
|
},
|
|
2281
3906
|
prewarm(text) {
|
|
2282
3907
|
void this.embed(text).catch(() => {
|
|
@@ -2292,7 +3917,8 @@ function getEmbedder() {
|
|
|
2292
3917
|
var EMBED_MODEL, ROUTE_THRESHOLD, EMBED_TIMEOUT_MS, CACHE_MAX, singleton, override;
|
|
2293
3918
|
var init_embedder = __esm({
|
|
2294
3919
|
"src/runtime/graph/embedder.ts"() {
|
|
2295
|
-
|
|
3920
|
+
init_log();
|
|
3921
|
+
init_llm();
|
|
2296
3922
|
EMBED_MODEL = process.env["VOICELAYER_EMBED_MODEL"] ?? "text-embedding-3-small";
|
|
2297
3923
|
ROUTE_THRESHOLD = Number(process.env["VL_FLOW_ROUTE_THRESHOLD"] ?? "0.62");
|
|
2298
3924
|
EMBED_TIMEOUT_MS = Number(process.env["VL_FLOW_EMBED_TIMEOUT_MS"] ?? "2500");
|
|
@@ -2354,7 +3980,7 @@ async function routeByEmbedding(nl, ctx) {
|
|
|
2354
3980
|
if (!best || score > best.score) best = { to: edge.to, score };
|
|
2355
3981
|
}
|
|
2356
3982
|
if (best && best.score >= ROUTE_THRESHOLD) {
|
|
2357
|
-
|
|
3983
|
+
graphLog("route: embedding", { to: best.to, score: Number(best.score.toFixed(3)) });
|
|
2358
3984
|
return best.to;
|
|
2359
3985
|
}
|
|
2360
3986
|
return null;
|
|
@@ -2380,11 +4006,21 @@ async function evaluateCondition(condition, ctx) {
|
|
|
2380
4006
|
function splitTop(text, op) {
|
|
2381
4007
|
return text.split(op);
|
|
2382
4008
|
}
|
|
4009
|
+
function conditionReadsSlots(condition) {
|
|
4010
|
+
const text = condition.trim();
|
|
4011
|
+
if (!text) return false;
|
|
4012
|
+
return splitTop(text, "||").flatMap((part) => splitTop(part, "&&")).map((atom) => atom.trim()).some((atom) => {
|
|
4013
|
+
const lower = atom.toLowerCase();
|
|
4014
|
+
if (lower === "true" || lower === "always" || lower === "false" || lower === "never") return false;
|
|
4015
|
+
if (TOOL_ATOM.test(atom) || NODE_ATOM.test(atom) || DTMF_ATOM.test(atom)) return false;
|
|
4016
|
+
return OP_ATOM.test(atom) || SLOT_PRESENCE_ATOM.test(atom) || BARE_ATOM.test(atom);
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
2383
4019
|
function evaluateDeterministic(atom, ctx) {
|
|
2384
4020
|
const lower = atom.toLowerCase();
|
|
2385
4021
|
if (lower === "true" || lower === "always") return true;
|
|
2386
4022
|
if (lower === "false" || lower === "never") return false;
|
|
2387
|
-
const toolMatch = atom.match(
|
|
4023
|
+
const toolMatch = atom.match(TOOL_ATOM);
|
|
2388
4024
|
if (toolMatch) {
|
|
2389
4025
|
const [, id, path, op, rawVal] = toolMatch;
|
|
2390
4026
|
const result = ctx.tools[id];
|
|
@@ -2398,7 +4034,7 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2398
4034
|
const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
|
|
2399
4035
|
return op === "!=" ? !eq : eq;
|
|
2400
4036
|
}
|
|
2401
|
-
const nodeMatch = atom.match(
|
|
4037
|
+
const nodeMatch = atom.match(NODE_ATOM);
|
|
2402
4038
|
if (nodeMatch) {
|
|
2403
4039
|
if (!ctx.nodeResults) return void 0;
|
|
2404
4040
|
const [, id, path, op, rawVal] = nodeMatch;
|
|
@@ -2410,14 +4046,12 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2410
4046
|
const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
|
|
2411
4047
|
return op === "!=" ? !eq : eq;
|
|
2412
4048
|
}
|
|
2413
|
-
const dtmfMatch = atom.match(
|
|
4049
|
+
const dtmfMatch = atom.match(DTMF_ATOM);
|
|
2414
4050
|
if (dtmfMatch) {
|
|
2415
4051
|
if (ctx.lastDtmf === void 0) return void 0;
|
|
2416
4052
|
return ctx.lastDtmf === dtmfMatch[1].trim();
|
|
2417
4053
|
}
|
|
2418
|
-
const opMatch = atom.match(
|
|
2419
|
-
/^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i
|
|
2420
|
-
);
|
|
4054
|
+
const opMatch = atom.match(OP_ATOM);
|
|
2421
4055
|
if (opMatch) {
|
|
2422
4056
|
const [, neg, nameRaw, op, valRaw] = opMatch;
|
|
2423
4057
|
const raw = resolvePath(ctx.slots, nameRaw.trim());
|
|
@@ -2439,14 +4073,14 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2439
4073
|
else res = value === target;
|
|
2440
4074
|
return neg ? !res : res;
|
|
2441
4075
|
}
|
|
2442
|
-
const slotPres = atom.match(
|
|
4076
|
+
const slotPres = atom.match(SLOT_PRESENCE_ATOM);
|
|
2443
4077
|
if (slotPres) {
|
|
2444
4078
|
const [, neg, nameRaw, kw] = slotPres;
|
|
2445
4079
|
const present = hasSlot(ctx.slots, nameRaw.trim());
|
|
2446
4080
|
const res = (kw ?? "").toLowerCase() === "empty" ? !present : present;
|
|
2447
4081
|
return neg ? !res : res;
|
|
2448
4082
|
}
|
|
2449
|
-
const bare = atom.match(
|
|
4083
|
+
const bare = atom.match(BARE_ATOM);
|
|
2450
4084
|
if (bare) {
|
|
2451
4085
|
const [, neg, name, kw] = bare;
|
|
2452
4086
|
const present = hasSlot(ctx.slots, name);
|
|
@@ -2457,13 +4091,27 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2457
4091
|
}
|
|
2458
4092
|
async function pickOption(options, ctx, hint) {
|
|
2459
4093
|
if (options.length === 0) return null;
|
|
2460
|
-
|
|
4094
|
+
return judgeSettled(ctx, (collected) => pickWith(options, collected, ctx.lastUtterance, hint));
|
|
4095
|
+
}
|
|
4096
|
+
function collectedOf(slots) {
|
|
4097
|
+
return Object.entries(slots).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(v)}`).join("; ");
|
|
4098
|
+
}
|
|
4099
|
+
async function judgeSettled(ctx, run) {
|
|
4100
|
+
const before = collectedOf(ctx.slots);
|
|
4101
|
+
if (!ctx.settle) return run(before);
|
|
4102
|
+
const [speculative, fresh] = await Promise.all([run(before), ctx.settle()]);
|
|
4103
|
+
const after = collectedOf(fresh);
|
|
4104
|
+
if (after === before) return speculative;
|
|
4105
|
+
graphLog("judge: capture changed context; re-judging");
|
|
4106
|
+
return run(after);
|
|
4107
|
+
}
|
|
4108
|
+
async function pickWith(options, collected, lastUtterance, hint) {
|
|
2461
4109
|
const list = options.map((o, i) => `${i + 1}. ${o.text}`).join("\n");
|
|
2462
4110
|
const prompt = [
|
|
2463
4111
|
"Pick which outcome best matches the current state of this voice conversation.",
|
|
2464
4112
|
...hint && hint.trim() ? [`What decides the path: ${hint.trim()}`] : [],
|
|
2465
4113
|
`Information collected: ${collected || "(none)"}`,
|
|
2466
|
-
`The caller most recently said: "${
|
|
4114
|
+
`The caller most recently said: "${lastUtterance ?? ""}"`,
|
|
2467
4115
|
"Outcomes:",
|
|
2468
4116
|
list,
|
|
2469
4117
|
'Answer with ONLY the outcome number, or "none" if nothing clearly matches.'
|
|
@@ -2488,7 +4136,7 @@ async function pickOption(options, ctx, hint) {
|
|
|
2488
4136
|
if (!Number.isInteger(n) || n < 1 || n > options.length) return null;
|
|
2489
4137
|
return options[n - 1]?.id ?? null;
|
|
2490
4138
|
} catch (err) {
|
|
2491
|
-
|
|
4139
|
+
graphWarn("option pick failed; treating as undecided", {
|
|
2492
4140
|
err: err instanceof Error ? err.message : String(err)
|
|
2493
4141
|
});
|
|
2494
4142
|
return null;
|
|
@@ -2497,12 +4145,18 @@ async function pickOption(options, ctx, hint) {
|
|
|
2497
4145
|
}
|
|
2498
4146
|
}
|
|
2499
4147
|
async function judge(condition, ctx) {
|
|
2500
|
-
|
|
4148
|
+
return judgeSettled(ctx, (collected) => judgeWith(condition, collected, ctx.lastUtterance));
|
|
4149
|
+
}
|
|
4150
|
+
async function judgeWith(condition, collected, lastUtterance) {
|
|
2501
4151
|
const prompt = [
|
|
2502
4152
|
"You judge whether a condition about an ongoing voice conversation is currently TRUE.",
|
|
2503
4153
|
`Information collected: ${collected || "(none)"}`,
|
|
2504
|
-
`The caller most recently said: "${
|
|
4154
|
+
`The caller most recently said: "${lastUtterance ?? ""}"`,
|
|
2505
4155
|
`Condition: ${condition}`,
|
|
4156
|
+
// Observed live: "Keystone Arena." (a site name) was judged to satisfy
|
|
4157
|
+
// "caller described a network problem". The judge must not fill in what
|
|
4158
|
+
// the caller probably meant — only what they actually said counts.
|
|
4159
|
+
'Judge only from what the caller actually said and the information collected \u2014 never infer or assume what they probably meant. If what they said does not itself establish the condition, answer "no".',
|
|
2506
4160
|
'Answer strictly "yes" or "no".'
|
|
2507
4161
|
].join("\n");
|
|
2508
4162
|
const controller = new AbortController();
|
|
@@ -2522,7 +4176,7 @@ async function judge(condition, ctx) {
|
|
|
2522
4176
|
);
|
|
2523
4177
|
return (res.choices[0]?.message?.content ?? "").trim().toLowerCase().startsWith("y");
|
|
2524
4178
|
} catch (err) {
|
|
2525
|
-
|
|
4179
|
+
graphWarn("condition judge failed; treating as false", {
|
|
2526
4180
|
condition,
|
|
2527
4181
|
err: err instanceof Error ? err.message : String(err)
|
|
2528
4182
|
});
|
|
@@ -2531,13 +4185,20 @@ async function judge(condition, ctx) {
|
|
|
2531
4185
|
clearTimeout(timer);
|
|
2532
4186
|
}
|
|
2533
4187
|
}
|
|
2534
|
-
var JUDGE_TIMEOUT_MS;
|
|
4188
|
+
var JUDGE_TIMEOUT_MS, TOOL_ATOM, NODE_ATOM, DTMF_ATOM, OP_ATOM, SLOT_PRESENCE_ATOM, BARE_ATOM;
|
|
2535
4189
|
var init_conditions = __esm({
|
|
2536
4190
|
"src/runtime/graph/conditions.ts"() {
|
|
2537
|
-
|
|
4191
|
+
init_log();
|
|
4192
|
+
init_llm();
|
|
2538
4193
|
init_interpolate();
|
|
2539
4194
|
init_embedder();
|
|
2540
4195
|
JUDGE_TIMEOUT_MS = 4e3;
|
|
4196
|
+
TOOL_ATOM = /^tool[.:]([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
|
|
4197
|
+
NODE_ATOM = /^node:([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
|
|
4198
|
+
DTMF_ATOM = /^dtmf:\s*(.+)$/i;
|
|
4199
|
+
OP_ATOM = /^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i;
|
|
4200
|
+
SLOT_PRESENCE_ATOM = /^(!?)\s*slot:(.+?)\s*(present|empty)?$/i;
|
|
4201
|
+
BARE_ATOM = /^(!?)\s*([\w.-]+)\s*(present|empty)?$/i;
|
|
2541
4202
|
}
|
|
2542
4203
|
});
|
|
2543
4204
|
|
|
@@ -2580,7 +4241,7 @@ async function resolveBranch(branches, ctx, opts) {
|
|
|
2580
4241
|
if (!best || score > best.score) best = { id: b.id, score };
|
|
2581
4242
|
}
|
|
2582
4243
|
if (best && best.score >= ROUTE_THRESHOLD) {
|
|
2583
|
-
|
|
4244
|
+
graphLog("branch: embedding", { id: best.id, score: Number(best.score.toFixed(3)) });
|
|
2584
4245
|
return best.id;
|
|
2585
4246
|
}
|
|
2586
4247
|
}
|
|
@@ -2595,6 +4256,7 @@ async function resolveBranch(branches, ctx, opts) {
|
|
|
2595
4256
|
var ELSE_BRANCH_ID;
|
|
2596
4257
|
var init_branch_resolve = __esm({
|
|
2597
4258
|
"src/runtime/graph/branch-resolve.ts"() {
|
|
4259
|
+
init_log();
|
|
2598
4260
|
init_conditions();
|
|
2599
4261
|
init_embedder();
|
|
2600
4262
|
ELSE_BRANCH_ID = "else";
|
|
@@ -2694,13 +4356,18 @@ function scopeFor(state, deps) {
|
|
|
2694
4356
|
function str(value) {
|
|
2695
4357
|
return typeof value === "string" ? value : "";
|
|
2696
4358
|
}
|
|
4359
|
+
async function settledSlots(deps) {
|
|
4360
|
+
await deps.utterances.settle?.();
|
|
4361
|
+
return deps.processRt.getData();
|
|
4362
|
+
}
|
|
2697
4363
|
function evalCtxOf(state, deps) {
|
|
2698
4364
|
return {
|
|
2699
4365
|
slots: deps.processRt.getData(),
|
|
2700
4366
|
tools: state.toolResults,
|
|
2701
4367
|
nodeResults: state.nodeResults,
|
|
2702
4368
|
...state.lastUtterance !== void 0 ? { lastUtterance: state.lastUtterance } : {},
|
|
2703
|
-
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
|
|
4369
|
+
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {},
|
|
4370
|
+
settle: () => settledSlots(deps)
|
|
2704
4371
|
};
|
|
2705
4372
|
}
|
|
2706
4373
|
function deterministicEdgeTarget(edges, ctx) {
|
|
@@ -2715,7 +4382,7 @@ async function speak(text, deps, opts) {
|
|
|
2715
4382
|
const allowInterruptions = opts?.allowInterruptions === true;
|
|
2716
4383
|
if (deps.ctx.adapter) {
|
|
2717
4384
|
const r = await deps.ctx.adapter.say(text, { allowInterruptions });
|
|
2718
|
-
|
|
4385
|
+
graphLog("said", { chars: text.length, interrupted: r.interrupted });
|
|
2719
4386
|
return;
|
|
2720
4387
|
}
|
|
2721
4388
|
const session = deps.ctx.livekit.session;
|
|
@@ -2725,12 +4392,12 @@ async function speak(text, deps, opts) {
|
|
|
2725
4392
|
|
|
2726
4393
|
${text}`
|
|
2727
4394
|
}).waitForPlayout();
|
|
2728
|
-
|
|
4395
|
+
graphLog("said (realtime)", { chars: text.length });
|
|
2729
4396
|
return;
|
|
2730
4397
|
}
|
|
2731
4398
|
const handle = session.say(text, { allowInterruptions });
|
|
2732
4399
|
await handle.waitForPlayout();
|
|
2733
|
-
|
|
4400
|
+
graphLog("said", { chars: text.length, interrupted: handle.interrupted });
|
|
2734
4401
|
}
|
|
2735
4402
|
async function genReply(deps, instructions) {
|
|
2736
4403
|
if (deps.ctx.adapter) {
|
|
@@ -2739,10 +4406,13 @@ async function genReply(deps, instructions) {
|
|
|
2739
4406
|
}
|
|
2740
4407
|
await deps.ctx.livekit.session.generateReply({ instructions }).waitForPlayout();
|
|
2741
4408
|
}
|
|
2742
|
-
async function nextTurn(deps, reprompt) {
|
|
4409
|
+
async function nextTurn(deps, reprompt, opts) {
|
|
2743
4410
|
let reprompted = false;
|
|
2744
4411
|
for (; ; ) {
|
|
2745
|
-
const evt = await deps.utterances.next({
|
|
4412
|
+
const evt = await deps.utterances.next({
|
|
4413
|
+
timeoutMs: NO_INPUT_MS,
|
|
4414
|
+
...opts?.deferCapture ? { deferCapture: true } : {}
|
|
4415
|
+
});
|
|
2746
4416
|
if (evt.kind === "hangup") return { result: { kind: "end" } };
|
|
2747
4417
|
if (evt.kind === "control") return { result: evt.result };
|
|
2748
4418
|
if (evt.kind === "timeout") {
|
|
@@ -2816,6 +4486,7 @@ function menuLabelOf(label) {
|
|
|
2816
4486
|
var ASK_MAX_CLARIFY, LLM_MAX_TURNS, HUB_MAX_TURNS, NO_INPUT_MS, sayExecutor, YES_WORDS, NO_WORDS, confirmExecutor, askExecutor, gateExecutor, endExecutor, handoffExecutor, llmExecutor, toolExecutor, ragExecutor, codeExecutor, decisionExecutor, MENU_MAX_INPUTS, menuExecutor, classifyExecutor, routerExecutor, playbookExecutor, setExecutor, MAX_SUBFLOW_DEPTH, subflowExecutor, passthroughExecutor, EXECUTORS;
|
|
2817
4487
|
var init_executors = __esm({
|
|
2818
4488
|
"src/runtime/graph/executors.ts"() {
|
|
4489
|
+
init_log();
|
|
2819
4490
|
init_src();
|
|
2820
4491
|
init_interpolate();
|
|
2821
4492
|
init_builtins();
|
|
@@ -2948,6 +4619,7 @@ var init_executors = __esm({
|
|
|
2948
4619
|
const policy = historyPolicyOf(node);
|
|
2949
4620
|
const maxTurns = hub ? HUB_MAX_TURNS : LLM_MAX_TURNS;
|
|
2950
4621
|
const base = interpolate(str(node.config.instructions), scopeFor(state, deps)) || "Help the caller using everything collected so far. Keep replies short and natural.";
|
|
4622
|
+
const deferCapture = !conditional.some((e) => conditionReadsSlots(e.condition));
|
|
2951
4623
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
2952
4624
|
const collected = formatCollected(deps.processRt.getData());
|
|
2953
4625
|
const parts = [base];
|
|
@@ -2960,25 +4632,19 @@ ${collected}`);
|
|
|
2960
4632
|
() => guardedGenerate(node, state, deps, parts.join("\n\n"), base)
|
|
2961
4633
|
);
|
|
2962
4634
|
if (!hub && !terminal && conditional.length === 0) return { kind: "advance" };
|
|
2963
|
-
const t = await nextTurn(deps);
|
|
4635
|
+
const t = await nextTurn(deps, void 0, { deferCapture });
|
|
2964
4636
|
if ("result" in t) return t.result;
|
|
2965
4637
|
state.lastUtterance = t.text;
|
|
2966
4638
|
state.lastDtmf = t.dtmf;
|
|
2967
4639
|
if (conditional.length > 0) {
|
|
2968
|
-
const target = await selectEdge(out,
|
|
2969
|
-
slots: deps.processRt.getData(),
|
|
2970
|
-
tools: state.toolResults,
|
|
2971
|
-
nodeResults: state.nodeResults,
|
|
2972
|
-
lastDtmf: state.lastDtmf,
|
|
2973
|
-
lastUtterance: state.lastUtterance
|
|
2974
|
-
});
|
|
4640
|
+
const target = await selectEdge(out, evalCtxOf(state, deps));
|
|
2975
4641
|
if (target) {
|
|
2976
|
-
|
|
4642
|
+
graphLog("llm: transition", { node: node.id, to: target, hub });
|
|
2977
4643
|
return { kind: "goto", to: target };
|
|
2978
4644
|
}
|
|
2979
4645
|
}
|
|
2980
4646
|
}
|
|
2981
|
-
|
|
4647
|
+
graphLog("llm: turn cap reached", { node: node.id, hub });
|
|
2982
4648
|
return terminal ? { kind: "end" } : { kind: "advance" };
|
|
2983
4649
|
};
|
|
2984
4650
|
toolExecutor = async (node, state, deps) => {
|
|
@@ -3337,7 +5003,7 @@ ${collected}`);
|
|
|
3337
5003
|
const goal = str(cfg.goal);
|
|
3338
5004
|
let instr = str(cfg.instructions);
|
|
3339
5005
|
const toolNames = Array.isArray(cfg.tools) ? cfg.tools.filter((x) => typeof x === "string" && x.length > 0) : [];
|
|
3340
|
-
let
|
|
5006
|
+
let model2 = str(cfg.model) || void 0;
|
|
3341
5007
|
let temperature = typeof cfg.temperature === "number" ? cfg.temperature : void 0;
|
|
3342
5008
|
let libraryExits = [];
|
|
3343
5009
|
const ref = str(cfg.playbookRef);
|
|
@@ -3347,7 +5013,7 @@ ${collected}`);
|
|
|
3347
5013
|
if (!instr) instr = resolved.instructions;
|
|
3348
5014
|
for (const f of resolved.requiredFields) if (!required.includes(f)) required.push(f);
|
|
3349
5015
|
for (const t of resolved.tools ?? []) if (!toolNames.includes(t)) toolNames.push(t);
|
|
3350
|
-
if (!
|
|
5016
|
+
if (!model2 && resolved.model) model2 = resolved.model;
|
|
3351
5017
|
if (temperature === void 0 && resolved.temperature !== void 0) temperature = resolved.temperature;
|
|
3352
5018
|
libraryExits = resolved.exits ?? [];
|
|
3353
5019
|
}
|
|
@@ -3401,7 +5067,7 @@ ${collected}`);
|
|
|
3401
5067
|
exits: exitOptions,
|
|
3402
5068
|
captureVars: required,
|
|
3403
5069
|
runTool: (name, args) => registry ? registry.run(name, args) : Promise.resolve({ ok: false, status: 0, data: { error: "no tool registry" } }),
|
|
3404
|
-
...
|
|
5070
|
+
...model2 ? { model: model2 } : {},
|
|
3405
5071
|
...temperature !== void 0 ? { temperature } : {}
|
|
3406
5072
|
});
|
|
3407
5073
|
} finally {
|
|
@@ -3594,18 +5260,18 @@ var interpreter_exports = {};
|
|
|
3594
5260
|
__export(interpreter_exports, {
|
|
3595
5261
|
runFlowGraph: () => runFlowGraph
|
|
3596
5262
|
});
|
|
3597
|
-
function pickEntry(
|
|
3598
|
-
return
|
|
5263
|
+
function pickEntry(model2) {
|
|
5264
|
+
return model2.entry.length > 0 ? model2.entry[0] : null;
|
|
3599
5265
|
}
|
|
3600
|
-
async function selectNext(
|
|
5266
|
+
async function selectNext(model2, from, result, state, deps) {
|
|
3601
5267
|
if (result.kind === "goto") return result.to;
|
|
3602
5268
|
if (result.kind === "take") {
|
|
3603
|
-
const target =
|
|
5269
|
+
const target = model2.selectByPort(from, result.port);
|
|
3604
5270
|
if (target !== null) return target;
|
|
3605
|
-
const def =
|
|
5271
|
+
const def = model2.out(from).find((e) => (!e.condition || !e.condition.trim()) && !e.sourceHandle);
|
|
3606
5272
|
return def ? def.to : null;
|
|
3607
5273
|
}
|
|
3608
|
-
return selectEdge(
|
|
5274
|
+
return selectEdge(model2.out(from), {
|
|
3609
5275
|
slots: deps.processRt.getData(),
|
|
3610
5276
|
tools: state.toolResults,
|
|
3611
5277
|
nodeResults: state.nodeResults,
|
|
@@ -3614,10 +5280,10 @@ async function selectNext(model, from, result, state, deps) {
|
|
|
3614
5280
|
});
|
|
3615
5281
|
}
|
|
3616
5282
|
async function runFlowGraph(program, deps) {
|
|
3617
|
-
const
|
|
5283
|
+
const model2 = buildGraphModel(program);
|
|
3618
5284
|
const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
|
|
3619
5285
|
const maxSteps = deps.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
3620
|
-
let cursor = pickEntry(
|
|
5286
|
+
let cursor = pickEntry(model2);
|
|
3621
5287
|
if (cursor === null) return { kind: "completed" };
|
|
3622
5288
|
const state = { toolResults: {}, nodeResults: {}, cursor };
|
|
3623
5289
|
const visits = /* @__PURE__ */ new Map();
|
|
@@ -3627,7 +5293,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3627
5293
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
|
|
3628
5294
|
break;
|
|
3629
5295
|
}
|
|
3630
|
-
const node =
|
|
5296
|
+
const node = model2.node(cursor);
|
|
3631
5297
|
if (!node) {
|
|
3632
5298
|
deps.events.note("flow.missing_node", { nodeId: cursor });
|
|
3633
5299
|
break;
|
|
@@ -3641,7 +5307,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3641
5307
|
}
|
|
3642
5308
|
state.cursor = cursor;
|
|
3643
5309
|
deps.events.enter(node.id, node.type);
|
|
3644
|
-
|
|
5310
|
+
graphLog("enter", { node: node.id, type: node.type, step: steps });
|
|
3645
5311
|
const executor = EXECUTORS[node.type] ?? passthroughExecutor;
|
|
3646
5312
|
let result;
|
|
3647
5313
|
try {
|
|
@@ -3650,7 +5316,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3650
5316
|
processRt: deps.processRt,
|
|
3651
5317
|
utterances: deps.utterances,
|
|
3652
5318
|
events: deps.events,
|
|
3653
|
-
model,
|
|
5319
|
+
model: model2,
|
|
3654
5320
|
...deps.history ? { history: deps.history } : {},
|
|
3655
5321
|
...deps.realtime ? { realtime: true } : {},
|
|
3656
5322
|
...deps.resolvers ? { resolvers: deps.resolvers } : {},
|
|
@@ -3664,17 +5330,19 @@ async function runFlowGraph(program, deps) {
|
|
|
3664
5330
|
return { kind: "ended", nodeId: node.id };
|
|
3665
5331
|
}
|
|
3666
5332
|
deps.events.note("node.errored", { nodeId: node.id, error: message });
|
|
3667
|
-
const recovery =
|
|
5333
|
+
const recovery = model2.selectByPort(node.id, "error");
|
|
3668
5334
|
if (recovery !== null) {
|
|
3669
5335
|
deps.events.traverse(node.id, recovery);
|
|
3670
5336
|
cursor = recovery;
|
|
3671
5337
|
continue;
|
|
3672
5338
|
}
|
|
3673
5339
|
return { kind: "error", nodeId: node.id, message };
|
|
5340
|
+
} finally {
|
|
5341
|
+
await deps.utterances.settle?.();
|
|
3674
5342
|
}
|
|
3675
5343
|
if (result.kind === "end") return { kind: "ended", nodeId: node.id };
|
|
3676
5344
|
if (result.kind === "handoff") return { kind: "handoff", to: result.to };
|
|
3677
|
-
const next = await selectNext(
|
|
5345
|
+
const next = await selectNext(model2, cursor, result, state, deps);
|
|
3678
5346
|
if (next !== null) deps.events.traverse(cursor, next);
|
|
3679
5347
|
cursor = next;
|
|
3680
5348
|
}
|
|
@@ -3683,6 +5351,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3683
5351
|
var DEFAULT_MAX_NODE_VISITS, DEFAULT_MAX_STEPS, HUB_MAX_NODE_VISITS;
|
|
3684
5352
|
var init_interpreter = __esm({
|
|
3685
5353
|
"src/runtime/graph/interpreter.ts"() {
|
|
5354
|
+
init_log();
|
|
3686
5355
|
init_graph_model();
|
|
3687
5356
|
init_executors();
|
|
3688
5357
|
init_conditions();
|
|
@@ -3693,15 +5362,33 @@ var init_interpreter = __esm({
|
|
|
3693
5362
|
});
|
|
3694
5363
|
|
|
3695
5364
|
// src/runtime/graph/index.ts
|
|
5365
|
+
init_log();
|
|
3696
5366
|
init_interpreter();
|
|
3697
5367
|
|
|
3698
5368
|
// src/runtime/graph/utterances.ts
|
|
5369
|
+
init_log();
|
|
3699
5370
|
var EVENT_UTTERANCE = "user_input_transcribed";
|
|
3700
5371
|
var EVENT_CLOSE = "close";
|
|
3701
|
-
|
|
5372
|
+
var DEFAULT_MIN_DELAY_MS = 600;
|
|
5373
|
+
var DEFAULT_MAX_DELAY_MS = 3e3;
|
|
5374
|
+
var VAD_SILENCE_MS = 550;
|
|
5375
|
+
var STT_LAG_MS = 400;
|
|
5376
|
+
function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtmf, onAwaitInput, endpointing) {
|
|
3702
5377
|
const queue = [];
|
|
3703
5378
|
let waiter = null;
|
|
3704
5379
|
let hung = false;
|
|
5380
|
+
let pending = null;
|
|
5381
|
+
const capture = async (text, deferred) => {
|
|
5382
|
+
const t0 = Date.now();
|
|
5383
|
+
try {
|
|
5384
|
+
await processRt.ingest(text, ctx);
|
|
5385
|
+
} catch {
|
|
5386
|
+
}
|
|
5387
|
+
if (deferred) graphLog("capture", { deferred: true, ms: Date.now() - t0 });
|
|
5388
|
+
};
|
|
5389
|
+
const settle = async () => {
|
|
5390
|
+
if (pending) await pending;
|
|
5391
|
+
};
|
|
3705
5392
|
const deliver = (e) => {
|
|
3706
5393
|
if (hung && e.kind !== "hangup") return;
|
|
3707
5394
|
if (waiter) {
|
|
@@ -3722,13 +5409,72 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3722
5409
|
};
|
|
3723
5410
|
const unsubs = [];
|
|
3724
5411
|
if (ctx.adapter) {
|
|
5412
|
+
let onSettle2 = function() {
|
|
5413
|
+
settle2 = null;
|
|
5414
|
+
settleDue = Number.POSITIVE_INFINITY;
|
|
5415
|
+
const idle = Date.now() - lastActivity;
|
|
5416
|
+
if (speaking && idle < minDelay + STT_LAG_MS) settleAt(minDelay + STT_LAG_MS - idle);
|
|
5417
|
+
else flush();
|
|
5418
|
+
};
|
|
3725
5419
|
const a = ctx.adapter;
|
|
5420
|
+
const voice2 = a.capabilities?.channel === "voice";
|
|
5421
|
+
const minDelay = Math.max(0, endpointing?.minDelay ?? DEFAULT_MIN_DELAY_MS);
|
|
5422
|
+
const maxDelay = Math.max(minDelay, endpointing?.maxDelay ?? DEFAULT_MAX_DELAY_MS);
|
|
5423
|
+
const parts = [];
|
|
5424
|
+
let speaking = false;
|
|
5425
|
+
let lastActivity = 0;
|
|
5426
|
+
let settle2 = null;
|
|
5427
|
+
let settleDue = Number.POSITIVE_INFINITY;
|
|
5428
|
+
let cap = null;
|
|
5429
|
+
const clearTimers = () => {
|
|
5430
|
+
if (settle2) clearTimeout(settle2);
|
|
5431
|
+
if (cap) clearTimeout(cap);
|
|
5432
|
+
settle2 = null;
|
|
5433
|
+
cap = null;
|
|
5434
|
+
settleDue = Number.POSITIVE_INFINITY;
|
|
5435
|
+
};
|
|
5436
|
+
const flush = () => {
|
|
5437
|
+
clearTimers();
|
|
5438
|
+
const text = parts.splice(0).join(" ").trim();
|
|
5439
|
+
if (!text) return;
|
|
5440
|
+
graphLog("turn", { chars: text.length, quietMs: Date.now() - lastActivity, vadSpeaking: speaking });
|
|
5441
|
+
deliver({ kind: "utterance", text });
|
|
5442
|
+
};
|
|
5443
|
+
const settleAt = (ms) => {
|
|
5444
|
+
if (settle2) clearTimeout(settle2);
|
|
5445
|
+
const wait = Math.max(0, ms);
|
|
5446
|
+
settleDue = Date.now() + wait;
|
|
5447
|
+
settle2 = setTimeout(onSettle2, wait);
|
|
5448
|
+
};
|
|
3726
5449
|
unsubs.push(
|
|
3727
5450
|
a.on("user_input", (e) => {
|
|
3728
|
-
if (
|
|
3729
|
-
|
|
5451
|
+
if (hung) return;
|
|
5452
|
+
lastActivity = Date.now();
|
|
5453
|
+
if (!e.isFinal) {
|
|
5454
|
+
if (parts.length > 0) settleAt(minDelay);
|
|
5455
|
+
return;
|
|
5456
|
+
}
|
|
5457
|
+
const text = e.transcript.trim();
|
|
5458
|
+
if (!text) return;
|
|
5459
|
+
if (!voice2 || minDelay === 0) {
|
|
5460
|
+
deliver({ kind: "utterance", text });
|
|
5461
|
+
return;
|
|
5462
|
+
}
|
|
5463
|
+
parts.push(text);
|
|
5464
|
+
if (cap) clearTimeout(cap);
|
|
5465
|
+
cap = setTimeout(flush, maxDelay);
|
|
5466
|
+
settleAt(minDelay);
|
|
3730
5467
|
})
|
|
3731
5468
|
);
|
|
5469
|
+
unsubs.push(
|
|
5470
|
+
a.on("user_state", (e) => {
|
|
5471
|
+
speaking = e.speaking;
|
|
5472
|
+
if (speaking || parts.length === 0) return;
|
|
5473
|
+
const due = e.at - VAD_SILENCE_MS + minDelay;
|
|
5474
|
+
if (due < settleDue) settleAt(due - Date.now());
|
|
5475
|
+
})
|
|
5476
|
+
);
|
|
5477
|
+
unsubs.push(clearTimers);
|
|
3732
5478
|
unsubs.push(a.once("close", onClose));
|
|
3733
5479
|
if (wantsDtmf) {
|
|
3734
5480
|
if (typeof ctx.onDtmf === "function") {
|
|
@@ -3796,10 +5542,12 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3796
5542
|
} else if (hung) {
|
|
3797
5543
|
return { kind: "hangup" };
|
|
3798
5544
|
} else {
|
|
5545
|
+
if (onAwaitInput) onAwaitInput();
|
|
3799
5546
|
raw = await awaitRaw(opts?.timeoutMs);
|
|
3800
5547
|
}
|
|
3801
5548
|
if (raw.kind === "hangup") return { kind: "hangup" };
|
|
3802
5549
|
if (raw.kind === "timeout") return { kind: "timeout" };
|
|
5550
|
+
await settle();
|
|
3803
5551
|
if (raw.kind === "dtmf") return { kind: "dtmf", digit: raw.digit };
|
|
3804
5552
|
if (rails) {
|
|
3805
5553
|
const outcome = await rails.intercept(raw.text, ctx);
|
|
@@ -3807,13 +5555,18 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3807
5555
|
if (outcome.kind === "control") return { kind: "control", result: outcome.result };
|
|
3808
5556
|
}
|
|
3809
5557
|
if (prewarm) prewarm(raw.text);
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
5558
|
+
if (opts?.deferCapture) {
|
|
5559
|
+
const run = capture(raw.text, true).then(() => {
|
|
5560
|
+
if (pending === run) pending = null;
|
|
5561
|
+
});
|
|
5562
|
+
pending = run;
|
|
5563
|
+
return { kind: "utterance", text: raw.text };
|
|
3813
5564
|
}
|
|
5565
|
+
await capture(raw.text, false);
|
|
3814
5566
|
return { kind: "utterance", text: raw.text };
|
|
3815
5567
|
}
|
|
3816
5568
|
},
|
|
5569
|
+
settle,
|
|
3817
5570
|
close() {
|
|
3818
5571
|
hung = true;
|
|
3819
5572
|
for (const u of unsubs) u();
|
|
@@ -3822,6 +5575,7 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3822
5575
|
}
|
|
3823
5576
|
|
|
3824
5577
|
// src/runtime/graph/history.ts
|
|
5578
|
+
init_log();
|
|
3825
5579
|
function isConversational(it) {
|
|
3826
5580
|
return it.type === "message" && (it.role === "user" || it.role === "assistant");
|
|
3827
5581
|
}
|
|
@@ -3861,7 +5615,7 @@ function createHistoryController(agent) {
|
|
|
3861
5615
|
next.items = prune(next.items, policy);
|
|
3862
5616
|
await agent.updateChatCtx(next);
|
|
3863
5617
|
} catch (err) {
|
|
3864
|
-
|
|
5618
|
+
graphWarn("historyPolicy prune failed; using full history", {
|
|
3865
5619
|
policy,
|
|
3866
5620
|
err: err instanceof Error ? err.message : String(err)
|
|
3867
5621
|
});
|
|
@@ -3874,7 +5628,7 @@ function createHistoryController(agent) {
|
|
|
3874
5628
|
try {
|
|
3875
5629
|
await agent.updateChatCtx(saved);
|
|
3876
5630
|
} catch (err) {
|
|
3877
|
-
|
|
5631
|
+
graphWarn("historyPolicy restore failed", {
|
|
3878
5632
|
err: err instanceof Error ? err.message : String(err)
|
|
3879
5633
|
});
|
|
3880
5634
|
}
|
|
@@ -3911,13 +5665,17 @@ function createGraphEvents(processRt) {
|
|
|
3911
5665
|
};
|
|
3912
5666
|
}
|
|
3913
5667
|
|
|
5668
|
+
// src/runtime/graph/rails.ts
|
|
5669
|
+
init_log();
|
|
5670
|
+
|
|
3914
5671
|
// src/runtime/graph/index.ts
|
|
3915
5672
|
init_conditions();
|
|
3916
5673
|
init_branch_resolve();
|
|
3917
5674
|
init_embedder();
|
|
3918
5675
|
|
|
3919
5676
|
// src/runtime/graph/agent-runner.ts
|
|
3920
|
-
|
|
5677
|
+
init_log();
|
|
5678
|
+
init_llm();
|
|
3921
5679
|
process.env["VOICELAYER_LLM_MODEL"] ?? process.env["OPENAI_LLM_MODEL"] ?? "gpt-4o";
|
|
3922
5680
|
Number(process.env["VL_FLOW_AGENT_TURN_TIMEOUT_MS"] ?? "30000");
|
|
3923
5681
|
Number(process.env["VL_FLOW_AGENT_MAX_TOOL_CALLS"] ?? "5");
|
|
@@ -3930,7 +5688,7 @@ init_graph_model();
|
|
|
3930
5688
|
// src/runtime/graph/resolvers.ts
|
|
3931
5689
|
init_src();
|
|
3932
5690
|
init_embedder();
|
|
3933
|
-
|
|
5691
|
+
init_llm();
|
|
3934
5692
|
Number(process.env["VL_FLOW_CODE_TIMEOUT_MS"] ?? "1000");
|
|
3935
5693
|
Number(process.env["VL_FLOW_CODE_MEMORY_BYTES"] ?? String(16 * 1024 * 1024));
|
|
3936
5694
|
|
|
@@ -3951,7 +5709,9 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
3951
5709
|
ctx,
|
|
3952
5710
|
opts.rails,
|
|
3953
5711
|
prewarm,
|
|
3954
|
-
wantsDtmf
|
|
5712
|
+
wantsDtmf,
|
|
5713
|
+
opts.onAwaitInput,
|
|
5714
|
+
opts.speech?.endpointing
|
|
3955
5715
|
);
|
|
3956
5716
|
const history = createHistoryController(opts.agent ?? null);
|
|
3957
5717
|
try {
|
|
@@ -3967,15 +5727,16 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
3967
5727
|
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
3968
5728
|
...opts.busy ? { busy: opts.busy } : {}
|
|
3969
5729
|
});
|
|
3970
|
-
|
|
5730
|
+
graphLog("outcome", outcome);
|
|
3971
5731
|
return outcome;
|
|
3972
5732
|
} finally {
|
|
5733
|
+
await utterances.settle?.();
|
|
3973
5734
|
utterances.close();
|
|
3974
5735
|
}
|
|
3975
5736
|
}
|
|
3976
5737
|
|
|
3977
5738
|
// src/runtime/process.ts
|
|
3978
|
-
|
|
5739
|
+
init_llm();
|
|
3979
5740
|
|
|
3980
5741
|
// ../primitives/process-schema/src/index.ts
|
|
3981
5742
|
init_src();
|
|
@@ -4871,6 +6632,10 @@ function createTextSessionAdapter(transport) {
|
|
|
4871
6632
|
await transport.send(text);
|
|
4872
6633
|
return { interrupted: false };
|
|
4873
6634
|
},
|
|
6635
|
+
async enqueueSay(text) {
|
|
6636
|
+
fire("agent_turn", { text });
|
|
6637
|
+
await transport.send(text);
|
|
6638
|
+
},
|
|
4874
6639
|
async generateReply(opts) {
|
|
4875
6640
|
if (transport.generate) {
|
|
4876
6641
|
const text = await transport.generate(opts?.instructions);
|
|
@@ -4949,6 +6714,7 @@ function processSchemaToDefinition(schema) {
|
|
|
4949
6714
|
}
|
|
4950
6715
|
|
|
4951
6716
|
// src/runtime/text-session.ts
|
|
6717
|
+
init_openai_default();
|
|
4952
6718
|
function buildTextContext(adapter, call, processRt) {
|
|
4953
6719
|
return {
|
|
4954
6720
|
call: {
|
|
@@ -5010,7 +6776,8 @@ function runTextSession(opts) {
|
|
|
5010
6776
|
// createUtteranceStream prefers ctx.adapter; the session arg is unused here.
|
|
5011
6777
|
session: {},
|
|
5012
6778
|
events,
|
|
5013
|
-
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
|
|
6779
|
+
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
|
|
6780
|
+
...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {}
|
|
5014
6781
|
});
|
|
5015
6782
|
return {
|
|
5016
6783
|
sendUserMessage: (text) => handle.push(text),
|
|
@@ -5056,12 +6823,12 @@ function definitionFor(opts) {
|
|
|
5056
6823
|
}
|
|
5057
6824
|
async function runTextTranscript(opts) {
|
|
5058
6825
|
const replies = [];
|
|
5059
|
-
const
|
|
6826
|
+
const trace2 = [];
|
|
5060
6827
|
const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
|
|
5061
6828
|
const session = runTextSession({
|
|
5062
6829
|
program: opts.program,
|
|
5063
6830
|
onAgentText: (t) => void replies.push(t),
|
|
5064
|
-
onTrace: (e) => void
|
|
6831
|
+
onTrace: (e) => void trace2.push(e),
|
|
5065
6832
|
...processRt ? { processRt } : {},
|
|
5066
6833
|
...opts.generate ? { generate: opts.generate } : {},
|
|
5067
6834
|
...opts.call ? { call: opts.call } : {},
|
|
@@ -5070,7 +6837,57 @@ async function runTextTranscript(opts) {
|
|
|
5070
6837
|
for (const m of opts.messages) session.sendUserMessage(m);
|
|
5071
6838
|
session.end();
|
|
5072
6839
|
const outcome = await session.result;
|
|
5073
|
-
return { replies, outcome, trace };
|
|
6840
|
+
return { replies, outcome, trace: trace2 };
|
|
6841
|
+
}
|
|
6842
|
+
function runLiveTextConversation(opts) {
|
|
6843
|
+
let buffer = [];
|
|
6844
|
+
let settle = null;
|
|
6845
|
+
let parkedPending = false;
|
|
6846
|
+
let ended = false;
|
|
6847
|
+
let outcome;
|
|
6848
|
+
const signal = () => {
|
|
6849
|
+
if (settle) {
|
|
6850
|
+
const s = settle;
|
|
6851
|
+
settle = null;
|
|
6852
|
+
s();
|
|
6853
|
+
} else {
|
|
6854
|
+
parkedPending = true;
|
|
6855
|
+
}
|
|
6856
|
+
};
|
|
6857
|
+
const processRt = opts.processRt ?? createProcessRuntime(definitionFor({ program: opts.program, ...opts.schema ? { schema: opts.schema } : {}, messages: [] }));
|
|
6858
|
+
const session = runTextSession({
|
|
6859
|
+
...opts,
|
|
6860
|
+
processRt,
|
|
6861
|
+
onAgentText: (t) => {
|
|
6862
|
+
buffer.push(t);
|
|
6863
|
+
return opts.onAgentText?.(t);
|
|
6864
|
+
},
|
|
6865
|
+
onAwaitInput: signal
|
|
6866
|
+
});
|
|
6867
|
+
void session.result.then((o) => {
|
|
6868
|
+
outcome = o;
|
|
6869
|
+
ended = true;
|
|
6870
|
+
signal();
|
|
6871
|
+
});
|
|
6872
|
+
const collect = async () => {
|
|
6873
|
+
if (!parkedPending) await new Promise((res) => settle = res);
|
|
6874
|
+
parkedPending = false;
|
|
6875
|
+
const replies = buffer.slice();
|
|
6876
|
+
buffer = [];
|
|
6877
|
+
return { replies, done: ended, ...ended && outcome ? { outcome } : {} };
|
|
6878
|
+
};
|
|
6879
|
+
return {
|
|
6880
|
+
start: () => collect(),
|
|
6881
|
+
async turn(text) {
|
|
6882
|
+
if (ended) return { replies: [], done: true, ...outcome ? { outcome } : {} };
|
|
6883
|
+
session.sendUserMessage(text);
|
|
6884
|
+
return collect();
|
|
6885
|
+
},
|
|
6886
|
+
async end() {
|
|
6887
|
+
session.end();
|
|
6888
|
+
return session.result;
|
|
6889
|
+
}
|
|
6890
|
+
};
|
|
5074
6891
|
}
|
|
5075
6892
|
|
|
5076
|
-
export { runTextSession, runTextTranscript };
|
|
6893
|
+
export { runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope };
|