@voicelayer/sdk 0.1.11 → 0.3.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/README.md +8 -0
- package/dist/brain/index.d.ts +283 -0
- package/dist/brain/index.js +3550 -0
- package/dist/index.d.ts +1472 -1588
- package/dist/index.js +9129 -6719
- package/dist/runtime/text-session.d.ts +2 -1
- package/dist/runtime/text-session.js +1969 -98
- package/dist/{text-session-D1j8nQac.d.ts → text-session-B7oyrhwQ.d.ts} +403 -308
- package/package.json +11 -6
|
@@ -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,16 @@ 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(),
|
|
496
|
+
// The text-turn protocol this worker speaks (burn-down G-4): it registers `<name>::text` and runs text turns as the
|
|
497
|
+
// agent. Absent = it can't (an older SDK, a puppet, a flow pool) — stored as null, so a downgrade stops text turns.
|
|
498
|
+
// Any version is accepted (a newer SDK never fails registration); the API decides which versions it dispatches to.
|
|
499
|
+
textTurns: z.number().int().min(1).max(1e3).optional(),
|
|
375
500
|
// Opt into MCP exposure. When true the API upserts a matching agent_config
|
|
376
501
|
// row keyed by (project_id, name, environment) so MCP / dashboard / REST
|
|
377
502
|
// can list and inspect this agent's identity. When false / omitted, the
|
|
@@ -391,6 +516,13 @@ var init_agents = __esm({
|
|
|
391
516
|
lastSeenAt: z.string().datetime().nullable(),
|
|
392
517
|
metadata: AgentMetadata,
|
|
393
518
|
processSchema: ProcessSchemaDTO.nullable().optional(),
|
|
519
|
+
// Whether this agent can run through the engine over text: a flow agent (its
|
|
520
|
+
// compiled graph rides in processSchema.program), a brain-connector agent, OR a
|
|
521
|
+
// code-first agent whose worker takes text turns (G-4). Drives the dashboard's chat gate so it
|
|
522
|
+
// no longer hard-codes "flow only". Computed server-side; not stored. OPTIONAL
|
|
523
|
+
// on the wire so a new worker validating a response from an OLD api pod during
|
|
524
|
+
// a rolling deploy doesn't fail register/heartbeat on a missing field.
|
|
525
|
+
textRunnable: z.boolean().optional(),
|
|
394
526
|
createdAt: z.string().datetime(),
|
|
395
527
|
updatedAt: z.string().datetime()
|
|
396
528
|
});
|
|
@@ -400,6 +532,9 @@ var init_agents = __esm({
|
|
|
400
532
|
z.object({
|
|
401
533
|
processSchema: ProcessSchemaDTO.nullable()
|
|
402
534
|
});
|
|
535
|
+
z.object({
|
|
536
|
+
prompt: z.string().nullable()
|
|
537
|
+
});
|
|
403
538
|
z.object({
|
|
404
539
|
agent: AgentDTO,
|
|
405
540
|
// Server-controlled cadence so we can tune without an SDK release.
|
|
@@ -486,7 +621,10 @@ var init_consultation = __esm({
|
|
|
486
621
|
consultationId: z.string().uuid(),
|
|
487
622
|
guidance: z.string().min(1).max(4e3),
|
|
488
623
|
// Optional bridge phrase the agent may speak before delivering guidance.
|
|
489
|
-
resumePhrase: z.string().max(500).optional()
|
|
624
|
+
resumePhrase: z.string().max(500).optional(),
|
|
625
|
+
// G-5: the API's id for this command; the agent confirms it (CallControlAck). Optional so a command from an older
|
|
626
|
+
// API is still acted on (just not confirmed).
|
|
627
|
+
commandId: z.string().uuid().optional()
|
|
490
628
|
});
|
|
491
629
|
ConsultationPolicy = z.object({
|
|
492
630
|
enabled: z.boolean().default(true),
|
|
@@ -511,7 +649,7 @@ var init_consultation = __esm({
|
|
|
511
649
|
});
|
|
512
650
|
}
|
|
513
651
|
});
|
|
514
|
-
var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
|
|
652
|
+
var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, commandId, SayCommand, HangupCommand, DtmfCommand, InstructCommand;
|
|
515
653
|
var init_call_events = __esm({
|
|
516
654
|
"../contracts/src/call-events.ts"() {
|
|
517
655
|
init_consultation();
|
|
@@ -536,7 +674,7 @@ var init_call_events = __esm({
|
|
|
536
674
|
"dtmf.received",
|
|
537
675
|
"dtmf.sent"
|
|
538
676
|
]);
|
|
539
|
-
OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
|
|
677
|
+
OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify|engine)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
|
|
540
678
|
OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
|
|
541
679
|
z.object({
|
|
542
680
|
reason: z.string().optional(),
|
|
@@ -581,8 +719,10 @@ var init_call_events = __esm({
|
|
|
581
719
|
ts: z.string().datetime()
|
|
582
720
|
});
|
|
583
721
|
z.enum(["say", "hangup", "dtmf", "instruct", "guidance"]);
|
|
722
|
+
commandId = z.string().uuid().optional();
|
|
584
723
|
SayCommand = z.object({
|
|
585
724
|
kind: z.literal("say"),
|
|
725
|
+
commandId,
|
|
586
726
|
text: z.string().min(1),
|
|
587
727
|
// 'interrupt' replaces any in-flight TTS; 'queue' waits for current to end.
|
|
588
728
|
mode: z.enum(["interrupt", "queue"]).default("interrupt"),
|
|
@@ -590,14 +730,17 @@ var init_call_events = __esm({
|
|
|
590
730
|
});
|
|
591
731
|
HangupCommand = z.object({
|
|
592
732
|
kind: z.literal("hangup"),
|
|
733
|
+
commandId,
|
|
593
734
|
reason: z.string().optional()
|
|
594
735
|
});
|
|
595
736
|
DtmfCommand = z.object({
|
|
596
737
|
kind: z.literal("dtmf"),
|
|
738
|
+
commandId,
|
|
597
739
|
digits: z.string().regex(/^[0-9*#]+$/)
|
|
598
740
|
});
|
|
599
741
|
InstructCommand = z.object({
|
|
600
742
|
kind: z.literal("instruct"),
|
|
743
|
+
commandId,
|
|
601
744
|
text: z.string().min(1)
|
|
602
745
|
});
|
|
603
746
|
z.discriminatedUnion("kind", [
|
|
@@ -607,6 +750,63 @@ var init_call_events = __esm({
|
|
|
607
750
|
InstructCommand,
|
|
608
751
|
GuidanceCommand
|
|
609
752
|
]);
|
|
753
|
+
z.object({
|
|
754
|
+
commandId: z.string().uuid(),
|
|
755
|
+
outcome: z.enum(["applied", "refused", "failed"]),
|
|
756
|
+
// machine code: output_blocked · not_supported · unknown_consultation · no_consultation · invalid_command ·
|
|
757
|
+
// say_unavailable · dtmf_unavailable
|
|
758
|
+
code: z.string().max(64).optional(),
|
|
759
|
+
detail: z.string().max(500).optional()
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
var TextTurnHistoryEntry, TextTurnUsage, TextTurnErrorCode;
|
|
764
|
+
var init_text_turns = __esm({
|
|
765
|
+
"../contracts/src/text-turns.ts"() {
|
|
766
|
+
z.object({
|
|
767
|
+
mode: z.literal("text"),
|
|
768
|
+
turnId: z.string().uuid(),
|
|
769
|
+
projectId: z.string().min(1),
|
|
770
|
+
agentId: z.string().uuid()
|
|
771
|
+
});
|
|
772
|
+
TextTurnHistoryEntry = z.object({
|
|
773
|
+
role: z.enum(["user", "assistant"]),
|
|
774
|
+
content: z.string().max(16e3)
|
|
775
|
+
});
|
|
776
|
+
z.object({
|
|
777
|
+
turnId: z.string().uuid(),
|
|
778
|
+
agentId: z.string().uuid(),
|
|
779
|
+
/** The Line the message came in on (`sms`, `discord`, `web`, `playground`, …). */
|
|
780
|
+
channel: z.string().min(1).max(40),
|
|
781
|
+
/** The sender on that Line (phone number, Discord user, widget visitor) — the agent's caller-scoped memory key. */
|
|
782
|
+
from: z.string().min(1).max(200),
|
|
783
|
+
/** The conversation this turn belongs to (stable across turns) — the tool context's call id. */
|
|
784
|
+
conversationKey: z.string().min(1).max(300),
|
|
785
|
+
history: z.array(TextTurnHistoryEntry).max(80),
|
|
786
|
+
text: z.string().min(1).max(8e3),
|
|
787
|
+
/** When the API stops waiting; the worker stops the run before it. */
|
|
788
|
+
deadlineAt: z.string().datetime()
|
|
789
|
+
});
|
|
790
|
+
TextTurnUsage = z.object({
|
|
791
|
+
provider: z.string().min(1).max(60),
|
|
792
|
+
model: z.string().min(1).max(200),
|
|
793
|
+
inputTokens: z.number().int().nonnegative(),
|
|
794
|
+
outputTokens: z.number().int().nonnegative(),
|
|
795
|
+
/** Only a first-party worker's label is used; a customer's worker runs on its own key (the API decides). */
|
|
796
|
+
byok: z.boolean()
|
|
797
|
+
});
|
|
798
|
+
TextTurnErrorCode = z.enum(["agent_unavailable", "agent_timeout"]);
|
|
799
|
+
z.union([
|
|
800
|
+
z.object({
|
|
801
|
+
agentId: z.string().uuid(),
|
|
802
|
+
replies: z.array(z.string().max(16e3)).max(20),
|
|
803
|
+
usage: z.array(TextTurnUsage).max(50).default([])
|
|
804
|
+
}),
|
|
805
|
+
z.object({
|
|
806
|
+
agentId: z.string().uuid(),
|
|
807
|
+
error: z.object({ code: TextTurnErrorCode, detail: z.string().max(500).optional() })
|
|
808
|
+
})
|
|
809
|
+
]);
|
|
610
810
|
}
|
|
611
811
|
});
|
|
612
812
|
var VoiceConfig, ModelConfig, SttConfig, RealtimeConfig, PipelineMode, ToolBinding, ProcessFieldType, ProcessFieldSpec, ProcessCompleteWhen, ProcessBackendAckSpec, ProcessSpec, TriggerConditionSpec, TriggerActionSpec, TriggerSpec, RequiredInfoField, AgentConfig;
|
|
@@ -1022,6 +1222,113 @@ var init_forms = __esm({
|
|
|
1022
1222
|
"../contracts/src/forms.ts"() {
|
|
1023
1223
|
}
|
|
1024
1224
|
});
|
|
1225
|
+
var AgentConfigVersionDTO, AgentConfigDiffField, AgentConfigChange;
|
|
1226
|
+
var init_agent_versions = __esm({
|
|
1227
|
+
"../contracts/src/agent-versions.ts"() {
|
|
1228
|
+
init_agent_config();
|
|
1229
|
+
init_agents();
|
|
1230
|
+
init_consultation();
|
|
1231
|
+
z.object({
|
|
1232
|
+
systemPrompt: z.string().max(64e3),
|
|
1233
|
+
routingInstructions: z.string().max(32e3).nullable(),
|
|
1234
|
+
voice: VoiceConfig,
|
|
1235
|
+
model: ModelConfig,
|
|
1236
|
+
stt: SttConfig.nullable(),
|
|
1237
|
+
mode: PipelineMode,
|
|
1238
|
+
realtime: RealtimeConfig.nullable(),
|
|
1239
|
+
language: z.string().min(2).max(16),
|
|
1240
|
+
tools: z.array(ToolBinding).max(64),
|
|
1241
|
+
consultation: ConsultationPolicy,
|
|
1242
|
+
tags: z.array(z.string().max(64)).max(32).nullable(),
|
|
1243
|
+
brainConnectorId: z.string().uuid().nullable(),
|
|
1244
|
+
flowVersion: z.number().int().nullable(),
|
|
1245
|
+
processSchema: ProcessSchemaDTO.nullable()
|
|
1246
|
+
});
|
|
1247
|
+
z.enum(["published", "draft"]);
|
|
1248
|
+
AgentConfigVersionDTO = z.object({
|
|
1249
|
+
id: z.string().uuid(),
|
|
1250
|
+
version: z.number().int().min(1),
|
|
1251
|
+
createdAt: z.string().datetime(),
|
|
1252
|
+
// 'user:<clerkId>' · 'api_key:<id>' · 'system:backfill' · 'system:deploy' · 'system:register'
|
|
1253
|
+
createdBy: z.string().nullable(),
|
|
1254
|
+
note: z.string().nullable(),
|
|
1255
|
+
flowVersion: z.number().int().nullable(),
|
|
1256
|
+
isPublished: z.boolean()
|
|
1257
|
+
});
|
|
1258
|
+
z.object({
|
|
1259
|
+
versions: z.array(AgentConfigVersionDTO),
|
|
1260
|
+
publishedVersion: z.number().int().min(1)
|
|
1261
|
+
});
|
|
1262
|
+
z.object({
|
|
1263
|
+
note: z.string().trim().max(500).optional()
|
|
1264
|
+
});
|
|
1265
|
+
z.object({
|
|
1266
|
+
version: AgentConfigVersionDTO
|
|
1267
|
+
});
|
|
1268
|
+
AgentConfigDiffField = z.enum([
|
|
1269
|
+
"systemPrompt",
|
|
1270
|
+
"routingInstructions",
|
|
1271
|
+
"mode",
|
|
1272
|
+
"model",
|
|
1273
|
+
"voice",
|
|
1274
|
+
"stt",
|
|
1275
|
+
"realtime",
|
|
1276
|
+
"language",
|
|
1277
|
+
"tools",
|
|
1278
|
+
"brainConnectorId",
|
|
1279
|
+
"consultation",
|
|
1280
|
+
"tags",
|
|
1281
|
+
"flowVersion",
|
|
1282
|
+
"processSchema"
|
|
1283
|
+
]);
|
|
1284
|
+
AgentConfigChange = z.object({
|
|
1285
|
+
field: AgentConfigDiffField,
|
|
1286
|
+
label: z.string(),
|
|
1287
|
+
/** The published value in plain words. */
|
|
1288
|
+
from: z.string(),
|
|
1289
|
+
/** The draft value in plain words. */
|
|
1290
|
+
to: z.string(),
|
|
1291
|
+
/** True for long text (prompts) — the sheet renders it in a scrollable block. */
|
|
1292
|
+
longText: z.boolean()
|
|
1293
|
+
});
|
|
1294
|
+
z.object({
|
|
1295
|
+
published: z.object({
|
|
1296
|
+
version: z.number().int().min(1),
|
|
1297
|
+
createdAt: z.string().datetime(),
|
|
1298
|
+
note: z.string().nullable()
|
|
1299
|
+
}),
|
|
1300
|
+
hasChanges: z.boolean(),
|
|
1301
|
+
changes: z.array(AgentConfigChange)
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// ../contracts/src/providers.ts
|
|
1307
|
+
function providerIdentity(id) {
|
|
1308
|
+
if (!id) return void 0;
|
|
1309
|
+
return PROVIDERS[PROVIDER_ALIASES[id] ?? id];
|
|
1310
|
+
}
|
|
1311
|
+
function byokProviders() {
|
|
1312
|
+
return Object.values(PROVIDERS).filter((p) => p.byok);
|
|
1313
|
+
}
|
|
1314
|
+
var PROVIDERS, PROVIDER_ALIASES;
|
|
1315
|
+
var init_providers = __esm({
|
|
1316
|
+
"../contracts/src/providers.ts"() {
|
|
1317
|
+
PROVIDERS = {
|
|
1318
|
+
openai: { id: "openai", label: "OpenAI", capabilities: ["llm", "tts", "realtime"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-..." },
|
|
1319
|
+
anthropic: { id: "anthropic", label: "Anthropic", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "sk-ant-..." },
|
|
1320
|
+
deepgram: { id: "deepgram", label: "Deepgram", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Deepgram API key" },
|
|
1321
|
+
elevenlabs: { id: "elevenlabs", label: "ElevenLabs", capabilities: ["tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "ElevenLabs API key" },
|
|
1322
|
+
cartesia: { id: "cartesia", label: "Cartesia", capabilities: ["stt", "tts"], byok: true, supportsBaseUrl: false, keyPlaceholder: "Cartesia API key" },
|
|
1323
|
+
assemblyai: { id: "assemblyai", label: "AssemblyAI", capabilities: ["stt"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AssemblyAI API key" },
|
|
1324
|
+
google: { id: "google", label: "Google (Gemini)", capabilities: ["llm", "realtime"], byok: true, supportsBaseUrl: false, keyPlaceholder: "AIza..." },
|
|
1325
|
+
groq: { id: "groq", label: "Groq", capabilities: ["llm"], byok: true, supportsBaseUrl: true, keyPlaceholder: "gsk_..." },
|
|
1326
|
+
// Runtime-only providers (no external key): local VAD.
|
|
1327
|
+
silero: { id: "silero", label: "Silero", capabilities: ["vad"], byok: false, supportsBaseUrl: false }
|
|
1328
|
+
};
|
|
1329
|
+
PROVIDER_ALIASES = { gemini: "google" };
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1025
1332
|
var ProviderKind, CatalogVoice, CatalogModel, CatalogProvider, CostEstimateAssumptions, CostEstimatePerMinute, CostEstimateBilledPerMinute;
|
|
1026
1333
|
var init_model_catalog = __esm({
|
|
1027
1334
|
"../contracts/src/model-catalog.ts"() {
|
|
@@ -1419,6 +1726,8 @@ var init_numbers = __esm({
|
|
|
1419
1726
|
agentId: z.string().uuid().nullable(),
|
|
1420
1727
|
// Environments pin (draft/live). null = no pin → live (today's behaviour).
|
|
1421
1728
|
agentConfigEnv: z.enum(["draft", "live"]).nullable(),
|
|
1729
|
+
// "Agent answers texts": the bound agent replies to inbound SMS (burn-down G-1). Off by default.
|
|
1730
|
+
smsAgentReplies: z.boolean(),
|
|
1422
1731
|
status: PhoneNumberStatus,
|
|
1423
1732
|
friendlyName: z.string().nullable(),
|
|
1424
1733
|
region: z.string().nullable(),
|
|
@@ -1442,6 +1751,7 @@ var init_numbers = __esm({
|
|
|
1442
1751
|
// null detaches.
|
|
1443
1752
|
agentId: z.string().uuid().nullable()
|
|
1444
1753
|
});
|
|
1754
|
+
z.object({ enabled: z.boolean() });
|
|
1445
1755
|
}
|
|
1446
1756
|
});
|
|
1447
1757
|
var init_project_limits = __esm({
|
|
@@ -1548,6 +1858,12 @@ var init_compliance = __esm({
|
|
|
1548
1858
|
ComplianceConfig.partial();
|
|
1549
1859
|
}
|
|
1550
1860
|
});
|
|
1861
|
+
|
|
1862
|
+
// ../contracts/src/sms-keywords.ts
|
|
1863
|
+
var init_sms_keywords = __esm({
|
|
1864
|
+
"../contracts/src/sms-keywords.ts"() {
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1551
1867
|
var init_call_recording = __esm({
|
|
1552
1868
|
"../contracts/src/call-recording.ts"() {
|
|
1553
1869
|
z.object({
|
|
@@ -1584,10 +1900,16 @@ var init_outbound = __esm({
|
|
|
1584
1900
|
var ConnectionAuthType, ConnectionStatus;
|
|
1585
1901
|
var init_connection = __esm({
|
|
1586
1902
|
"../contracts/src/connection.ts"() {
|
|
1903
|
+
init_providers();
|
|
1587
1904
|
ConnectionAuthType = z.enum(["oauth2", "api_key", "bearer", "basic", "none"]);
|
|
1588
1905
|
ConnectionStatus = z.enum(["active", "revoked", "error"]);
|
|
1589
1906
|
z.object({
|
|
1590
1907
|
v: z.number().int(),
|
|
1908
|
+
// Key id (short fingerprint) of the key that encrypted this envelope — lets the
|
|
1909
|
+
// cipher pick the right key from its ring so CREDENTIAL_ENCRYPTION_KEY can rotate
|
|
1910
|
+
// in-band (Tier-1 item 4). Optional/absent on legacy v1 envelopes, which decrypt
|
|
1911
|
+
// with the designated legacy key.
|
|
1912
|
+
kid: z.string().optional(),
|
|
1591
1913
|
iv: z.string(),
|
|
1592
1914
|
ct: z.string(),
|
|
1593
1915
|
tag: z.string()
|
|
@@ -1625,6 +1947,12 @@ var init_connection = __esm({
|
|
|
1625
1947
|
}).refine((v) => v.name !== void 0 || v.secret !== void 0 || v.metadata !== void 0, {
|
|
1626
1948
|
message: "nothing to update"
|
|
1627
1949
|
});
|
|
1950
|
+
byokProviders().map((p) => ({
|
|
1951
|
+
id: p.id,
|
|
1952
|
+
label: p.label,
|
|
1953
|
+
keyPlaceholder: p.keyPlaceholder ?? "",
|
|
1954
|
+
supportsBaseUrl: p.supportsBaseUrl
|
|
1955
|
+
}));
|
|
1628
1956
|
}
|
|
1629
1957
|
});
|
|
1630
1958
|
var E164, RoomPrefix, RecordingPrefix, Cidr, TelephonySettings, ProfileSource, EffectiveCallProfile;
|
|
@@ -1958,6 +2286,7 @@ var init_flow_enrichment = __esm({
|
|
|
1958
2286
|
var init_flow_compile = __esm({
|
|
1959
2287
|
"../contracts/src/flow-compile.ts"() {
|
|
1960
2288
|
init_flow_enrichment();
|
|
2289
|
+
init_flow_validate();
|
|
1961
2290
|
}
|
|
1962
2291
|
});
|
|
1963
2292
|
var EnvironmentSpec;
|
|
@@ -2102,6 +2431,1057 @@ var init_playbook_compile = __esm({
|
|
|
2102
2431
|
"../contracts/src/playbook-compile.ts"() {
|
|
2103
2432
|
}
|
|
2104
2433
|
});
|
|
2434
|
+
var EngineSeamBinding, EnginePluginDTO;
|
|
2435
|
+
var init_engine = __esm({
|
|
2436
|
+
"../contracts/src/engine.ts"() {
|
|
2437
|
+
EngineSeamBinding = z.object({
|
|
2438
|
+
key: z.string(),
|
|
2439
|
+
bound: z.boolean()
|
|
2440
|
+
});
|
|
2441
|
+
EnginePluginDTO = z.object({
|
|
2442
|
+
name: z.string(),
|
|
2443
|
+
provides: z.array(EngineSeamBinding),
|
|
2444
|
+
injects: z.array(z.string())
|
|
2445
|
+
});
|
|
2446
|
+
z.object({
|
|
2447
|
+
plugins: z.array(EnginePluginDTO)
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
|
|
2452
|
+
// ../contracts/src/identity.ts
|
|
2453
|
+
var init_identity = __esm({
|
|
2454
|
+
"../contracts/src/identity.ts"() {
|
|
2455
|
+
}
|
|
2456
|
+
});
|
|
2457
|
+
|
|
2458
|
+
// ../contracts/src/interaction.ts
|
|
2459
|
+
var init_interaction = __esm({
|
|
2460
|
+
"../contracts/src/interaction.ts"() {
|
|
2461
|
+
}
|
|
2462
|
+
});
|
|
2463
|
+
|
|
2464
|
+
// ../contracts/src/channel-connector/capability-state.ts
|
|
2465
|
+
var CONDITION_CLASSES, CONDITION_TYPES, SURFACES, TRANSPORT_KINDS, AGENT_INITIATION, TEMPLATE_REQUIREMENT, WINDOW_OUTSIDE, PEER_ID_SCOPES, RECEIPT_KINDS;
|
|
2466
|
+
var init_capability_state = __esm({
|
|
2467
|
+
"../contracts/src/channel-connector/capability-state.ts"() {
|
|
2468
|
+
CONDITION_CLASSES = ["authority", "operational", "observability"];
|
|
2469
|
+
CONDITION_TYPES = [
|
|
2470
|
+
{ id: "CredentialsValid", defaultClass: "authority", summary: "credential lease resolves and is not expired/revoked" },
|
|
2471
|
+
{ id: "ProviderAccountGood", defaultClass: "authority", summary: "provider account / sender standing is not suspended or restricted" },
|
|
2472
|
+
{ id: "RegistrationApproved", defaultClass: "authority", summary: "required registration / approval is current" },
|
|
2473
|
+
{ id: "ResourceOwnershipConfirmed", defaultClass: "authority", summary: "the native object is confirmed to belong to this principal" },
|
|
2474
|
+
{ id: "InstallationPresent", defaultClass: "authority", summary: "the container installation exists and is not revoked" },
|
|
2475
|
+
{ id: "PermissionsHeld", defaultClass: "authority", summary: "required provider permissions/scopes/intents are held" },
|
|
2476
|
+
{ id: "ConsentResolved", defaultClass: "authority", summary: "consent verdict available (ch. 02; not_evaluated until it ships)" },
|
|
2477
|
+
{ id: "WindowOpen", defaultClass: "authority", summary: "the reply window for the peer is open" },
|
|
2478
|
+
{ id: "TemplateAvailable", defaultClass: "authority", summary: "an approved template exists for the operation" },
|
|
2479
|
+
{ id: "PeerReachable", defaultClass: "authority", summary: "the peer can be reached on this channel (e.g. RCS capability, not blocked)" },
|
|
2480
|
+
{ id: "WebhookRegistered", defaultClass: "operational", summary: "the provider has our ingress registered" },
|
|
2481
|
+
{ 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)" },
|
|
2482
|
+
{ id: "WebhookHealthy", defaultClass: "operational", summary: "recent verified inbound / delivery health" },
|
|
2483
|
+
{ id: "TransportConnected", defaultClass: "operational", summary: "a transport lease is live (gateway / long-poll / pub-sub)" },
|
|
2484
|
+
{ id: "ReconcileFresh", defaultClass: "operational", summary: "the last reconcile is within its window" },
|
|
2485
|
+
{ id: "ProviderLatency", defaultClass: "observability", summary: "provider latency / error-rate telemetry" }
|
|
2486
|
+
];
|
|
2487
|
+
new Set(CONDITION_TYPES.map((c) => c.id));
|
|
2488
|
+
SURFACES = ["direct", "group", "channel", "thread", "broadcast"];
|
|
2489
|
+
TRANSPORT_KINDS = ["webhook", "long_poll", "gateway", "pubsub", "http_interactions"];
|
|
2490
|
+
AGENT_INITIATION = ["always", "installed_only", "opt_in_required", "template_only", "never"];
|
|
2491
|
+
TEMPLATE_REQUIREMENT = ["never", "outside_window", "always"];
|
|
2492
|
+
WINDOW_OUTSIDE = ["tagged_only", "template_only", "blocked"];
|
|
2493
|
+
PEER_ID_SCOPES = ["global", "app_scoped", "workspace_scoped", "installation_scoped"];
|
|
2494
|
+
RECEIPT_KINDS = ["sent", "delivered", "read", "failed"];
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
|
|
2498
|
+
// ../contracts/src/channel-connector/channels.ts
|
|
2499
|
+
function baseline(channel, partial) {
|
|
2500
|
+
return {
|
|
2501
|
+
channel,
|
|
2502
|
+
connector: null,
|
|
2503
|
+
version: "1.0",
|
|
2504
|
+
requiredPermissions: [],
|
|
2505
|
+
registration: [],
|
|
2506
|
+
readinessGates: [
|
|
2507
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2508
|
+
{ condition: "ProviderAccountGood", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2509
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 900, evidence: "runtime" }
|
|
2510
|
+
],
|
|
2511
|
+
...partial
|
|
2512
|
+
};
|
|
2513
|
+
}
|
|
2514
|
+
var TEXT_ONLY, RICH_MESSAGING, MESSAGE_CORE, CONTAINER_GATES, CHANNELS, CHANNEL_IDS;
|
|
2515
|
+
var init_channels = __esm({
|
|
2516
|
+
"../contracts/src/channel-connector/channels.ts"() {
|
|
2517
|
+
init_capability_state();
|
|
2518
|
+
TEXT_ONLY = ["text"];
|
|
2519
|
+
RICH_MESSAGING = ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "template"];
|
|
2520
|
+
MESSAGE_CORE = ["receive.message", "send.text", "send.media", "initiate.conversation", "reply.in_window"];
|
|
2521
|
+
CONTAINER_GATES = [
|
|
2522
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2523
|
+
{ condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2524
|
+
{ condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "group.post"], ttlSec: 3600 },
|
|
2525
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
|
|
2526
|
+
];
|
|
2527
|
+
CHANNELS = [
|
|
2528
|
+
{
|
|
2529
|
+
id: "sms",
|
|
2530
|
+
displayName: "SMS",
|
|
2531
|
+
addressSpace: "phone",
|
|
2532
|
+
surfaces: ["direct"],
|
|
2533
|
+
bindingCapabilities: ["sms"],
|
|
2534
|
+
baseline: baseline("sms", {
|
|
2535
|
+
operations: [...MESSAGE_CORE, "read.receipt"],
|
|
2536
|
+
direction: { inbound: true, outbound: true },
|
|
2537
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated with registration (10DLC / TFV per country); STOP/HELP keywords mandatory" },
|
|
2538
|
+
windows: [],
|
|
2539
|
+
templates: { required: "never", reviewed: false },
|
|
2540
|
+
surfaces: ["direct"],
|
|
2541
|
+
messageTypes: TEXT_ONLY,
|
|
2542
|
+
// GSM-7 160/153, UCS-2 70/67 per segment (doc 01 §3); no body cap is a channel fact.
|
|
2543
|
+
limits: { segments: true, note: "GSM-7 160/153 \xB7 UCS-2 70/67 per segment" },
|
|
2544
|
+
receipts: ["sent", "delivered", "failed"],
|
|
2545
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2546
|
+
transport: ["webhook"],
|
|
2547
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2548
|
+
registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
|
|
2549
|
+
})
|
|
2550
|
+
},
|
|
2551
|
+
{
|
|
2552
|
+
id: "mms",
|
|
2553
|
+
displayName: "MMS",
|
|
2554
|
+
addressSpace: "phone",
|
|
2555
|
+
surfaces: ["direct", "group"],
|
|
2556
|
+
bindingCapabilities: ["mms"],
|
|
2557
|
+
baseline: baseline("mms", {
|
|
2558
|
+
operations: [...MESSAGE_CORE, "group.post"],
|
|
2559
|
+
direction: { inbound: true, outbound: true },
|
|
2560
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "as SMS; carrier MMS size caps apply" },
|
|
2561
|
+
windows: [],
|
|
2562
|
+
templates: { required: "never", reviewed: false },
|
|
2563
|
+
surfaces: ["direct", "group"],
|
|
2564
|
+
messageTypes: ["text", "image", "audio", "video", "document"],
|
|
2565
|
+
limits: { mediaBytes: 1e6, note: "carrier MMS \u22480.6\u20131 MB (doc 01 \xA73)" },
|
|
2566
|
+
receipts: ["sent", "delivered", "failed"],
|
|
2567
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2568
|
+
transport: ["webhook"],
|
|
2569
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2570
|
+
registration: [{ kind: "campaign_vetting", blocks: ["send.text", "send.media", "initiate.conversation"] }]
|
|
2571
|
+
})
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
id: "voice",
|
|
2575
|
+
displayName: "Voice (PSTN)",
|
|
2576
|
+
addressSpace: "phone",
|
|
2577
|
+
surfaces: ["direct"],
|
|
2578
|
+
bindingCapabilities: ["voice"],
|
|
2579
|
+
baseline: baseline("voice", {
|
|
2580
|
+
operations: ["call.receive", "call.place"],
|
|
2581
|
+
direction: { inbound: true, outbound: true },
|
|
2582
|
+
initiation: { agentInitiated: "always", userInitiated: true, firstContactRule: "agent-initiated; STIR/SHAKEN attestation; emergency addressing; trunk config" },
|
|
2583
|
+
windows: [],
|
|
2584
|
+
templates: { required: "never", reviewed: false },
|
|
2585
|
+
surfaces: ["direct"],
|
|
2586
|
+
messageTypes: [],
|
|
2587
|
+
limits: {},
|
|
2588
|
+
receipts: [],
|
|
2589
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2590
|
+
transport: ["webhook"],
|
|
2591
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" }
|
|
2592
|
+
})
|
|
2593
|
+
},
|
|
2594
|
+
{
|
|
2595
|
+
id: "whatsapp",
|
|
2596
|
+
displayName: "WhatsApp",
|
|
2597
|
+
addressSpace: "whatsapp",
|
|
2598
|
+
surfaces: ["direct", "group"],
|
|
2599
|
+
bindingCapabilities: ["whatsapp.message"],
|
|
2600
|
+
baseline: baseline("whatsapp", {
|
|
2601
|
+
operations: [...MESSAGE_CORE, "send.template", "send.interactive", "send.reaction", "read.receipt", "typing.indicate"],
|
|
2602
|
+
direction: { inbound: true, outbound: true },
|
|
2603
|
+
initiation: { agentInitiated: "template_only", userInitiated: true, firstContactRule: "user-initiated or a reviewed template outside the 24-h window; opt-in mandatory" },
|
|
2604
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "template_only" }],
|
|
2605
|
+
templates: { required: "outside_window", reviewed: true },
|
|
2606
|
+
surfaces: ["direct", "group"],
|
|
2607
|
+
messageTypes: RICH_MESSAGING,
|
|
2608
|
+
limits: {
|
|
2609
|
+
textChars: 4096,
|
|
2610
|
+
mediaBytes: 1e8,
|
|
2611
|
+
perSecond: 80,
|
|
2612
|
+
formats: { audio: { mime: ["audio/ogg", "audio/mpeg", "audio/mp4", "audio/aac", "audio/amr"], variants: ["voice_note", "file"] } }
|
|
2613
|
+
},
|
|
2614
|
+
receipts: ["sent", "delivered", "read", "failed"],
|
|
2615
|
+
edits: { edit: false, delete: false, reactions: true, commands: false },
|
|
2616
|
+
transport: ["webhook"],
|
|
2617
|
+
identity: { peerIdKind: "whatsapp", peerIdScope: "global" },
|
|
2618
|
+
registration: [{ kind: "business_verification", blocks: ["initiate.conversation", "send.template"] }]
|
|
2619
|
+
})
|
|
2620
|
+
},
|
|
2621
|
+
{
|
|
2622
|
+
id: "instagram",
|
|
2623
|
+
displayName: "Instagram DM",
|
|
2624
|
+
addressSpace: "app",
|
|
2625
|
+
surfaces: ["direct"],
|
|
2626
|
+
bindingCapabilities: ["instagram.message"],
|
|
2627
|
+
baseline: baseline("instagram", {
|
|
2628
|
+
operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "delete.message", "read.receipt"],
|
|
2629
|
+
direction: { inbound: true, outbound: true },
|
|
2630
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "user-initiated only \u2014 the peer identifier exists only after the user messages or clicks" },
|
|
2631
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
|
|
2632
|
+
templates: { required: "never", reviewed: false },
|
|
2633
|
+
surfaces: ["direct"],
|
|
2634
|
+
messageTypes: ["text", "image", "audio", "video", "document", "sticker", "reaction", "interactive", "template"],
|
|
2635
|
+
limits: { textBytes: 1e3, mediaBytes: 25e6 },
|
|
2636
|
+
receipts: ["read"],
|
|
2637
|
+
edits: { edit: false, delete: true, reactions: true, commands: false },
|
|
2638
|
+
transport: ["webhook"],
|
|
2639
|
+
// doc 07 §3: per person × professional account, stable across apps — the
|
|
2640
|
+
// account scope is embedded in the normalized value, so the id is global.
|
|
2641
|
+
identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
|
|
2642
|
+
registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
|
|
2643
|
+
})
|
|
2644
|
+
},
|
|
2645
|
+
{
|
|
2646
|
+
id: "messenger",
|
|
2647
|
+
displayName: "Messenger",
|
|
2648
|
+
addressSpace: "app",
|
|
2649
|
+
surfaces: ["direct"],
|
|
2650
|
+
bindingCapabilities: ["messenger.message"],
|
|
2651
|
+
baseline: baseline("messenger", {
|
|
2652
|
+
operations: ["receive.message", "send.text", "send.media", "send.reaction", "reply.in_window", "edit.message", "read.receipt", "typing.indicate"],
|
|
2653
|
+
direction: { inbound: true, outbound: true },
|
|
2654
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "as Instagram plus Get Started / link / ads entry points" },
|
|
2655
|
+
windows: [{ kind: "reply", durationSec: 86400, opensOn: "inbound_message", outside: "tagged_only" }],
|
|
2656
|
+
templates: { required: "never", reviewed: false },
|
|
2657
|
+
surfaces: ["direct"],
|
|
2658
|
+
messageTypes: RICH_MESSAGING,
|
|
2659
|
+
limits: { mediaBytes: 25e6 },
|
|
2660
|
+
receipts: ["delivered", "read"],
|
|
2661
|
+
edits: { edit: true, delete: false, reactions: true, commands: false },
|
|
2662
|
+
transport: ["webhook"],
|
|
2663
|
+
identity: { peerIdKind: "meta_account_user", peerIdScope: "global" },
|
|
2664
|
+
registration: [{ kind: "app_review", blocks: ["send.text", "send.media"] }]
|
|
2665
|
+
})
|
|
2666
|
+
},
|
|
2667
|
+
{
|
|
2668
|
+
id: "telegram",
|
|
2669
|
+
displayName: "Telegram",
|
|
2670
|
+
addressSpace: "app",
|
|
2671
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2672
|
+
bindingCapabilities: ["telegram.message"],
|
|
2673
|
+
baseline: baseline("telegram", {
|
|
2674
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "group.post", "channel.post", "thread.reply"],
|
|
2675
|
+
direction: { inbound: true, outbound: true },
|
|
2676
|
+
// Plain bot: the user must start; no reply window. The business-account
|
|
2677
|
+
// 24-h rule is an installation-level narrowing, not the channel ceiling.
|
|
2678
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the user must start the bot (/start, deep link, group add) before it may message them" },
|
|
2679
|
+
windows: [],
|
|
2680
|
+
templates: { required: "never", reviewed: false },
|
|
2681
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2682
|
+
messageTypes: ["text", "image", "audio", "video", "document", "location", "contact", "sticker", "reaction", "interactive", "poll"],
|
|
2683
|
+
limits: { textChars: 4096, mediaBytes: 5e7, formats: { audio: { mime: ["audio/ogg", "audio/mpeg"], variants: ["voice_note", "file"] } } },
|
|
2684
|
+
receipts: [],
|
|
2685
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2686
|
+
transport: ["webhook", "long_poll"],
|
|
2687
|
+
identity: { peerIdKind: "telegram_user", peerIdScope: "global" }
|
|
2688
|
+
})
|
|
2689
|
+
},
|
|
2690
|
+
{
|
|
2691
|
+
id: "discord",
|
|
2692
|
+
displayName: "Discord",
|
|
2693
|
+
addressSpace: "app",
|
|
2694
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2695
|
+
bindingCapabilities: ["discord.message", "discord.command"],
|
|
2696
|
+
baseline: baseline("discord", {
|
|
2697
|
+
// The channel exposes BOTH modes (doc 04 §1): text via `send.*`, realtime
|
|
2698
|
+
// audio via `call.*`. Mode is per OPERATION — the channel id never implies it.
|
|
2699
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply", "call.receive", "call.place"],
|
|
2700
|
+
direction: { inbound: true, outbound: true },
|
|
2701
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "guild channels by permission; DMs need a mutual guild or a user install" },
|
|
2702
|
+
windows: [],
|
|
2703
|
+
templates: { required: "never", reviewed: false },
|
|
2704
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2705
|
+
messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card", "poll"],
|
|
2706
|
+
limits: { textChars: 2e3, mediaBytes: 10485760 },
|
|
2707
|
+
receipts: [],
|
|
2708
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2709
|
+
transport: ["gateway", "http_interactions"],
|
|
2710
|
+
identity: { peerIdKind: "discord_user", peerIdScope: "global" },
|
|
2711
|
+
readinessGates: [
|
|
2712
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2713
|
+
{ condition: "InstallationPresent", class: "authority", blocks: "*", ttlSec: 3600 },
|
|
2714
|
+
{ condition: "PermissionsHeld", class: "authority", blocks: ["send.text", "send.media", "channel.post", "thread.reply", "call.receive", "call.place"], ttlSec: 3600 },
|
|
2715
|
+
{ condition: "TransportConnected", class: "operational", blocks: ["receive.message", "call.receive"], ttlSec: 120 }
|
|
2716
|
+
]
|
|
2717
|
+
})
|
|
2718
|
+
},
|
|
2719
|
+
{
|
|
2720
|
+
// G-3: the embeddable website chat widget. The widget key IS the identity and its evidence (a `web`-space Binding
|
|
2721
|
+
// at `widget:<keyId>`, no provider connection), so no provider readiness gate applies. The agent only ever answers
|
|
2722
|
+
// inside the visitor's own request — it never starts or pushes a message.
|
|
2723
|
+
id: "web",
|
|
2724
|
+
displayName: "Website chat",
|
|
2725
|
+
addressSpace: "web",
|
|
2726
|
+
surfaces: ["direct"],
|
|
2727
|
+
bindingCapabilities: ["web.message"],
|
|
2728
|
+
baseline: baseline("web", {
|
|
2729
|
+
operations: ["receive.message", "reply.in_window"],
|
|
2730
|
+
direction: { inbound: true, outbound: true },
|
|
2731
|
+
initiation: { agentInitiated: "never", userInitiated: true, firstContactRule: "the visitor opens the widget and writes first" },
|
|
2732
|
+
windows: [],
|
|
2733
|
+
templates: { required: "never", reviewed: false },
|
|
2734
|
+
surfaces: ["direct"],
|
|
2735
|
+
messageTypes: TEXT_ONLY,
|
|
2736
|
+
limits: { textChars: 8e3 },
|
|
2737
|
+
receipts: [],
|
|
2738
|
+
edits: { edit: false, delete: false, reactions: false, commands: false },
|
|
2739
|
+
transport: ["http_interactions"],
|
|
2740
|
+
identity: { peerIdKind: "web_visitor", peerIdScope: "app_scoped" },
|
|
2741
|
+
readinessGates: []
|
|
2742
|
+
})
|
|
2743
|
+
},
|
|
2744
|
+
{
|
|
2745
|
+
id: "slack",
|
|
2746
|
+
displayName: "Slack",
|
|
2747
|
+
addressSpace: "app",
|
|
2748
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2749
|
+
bindingCapabilities: ["slack.message"],
|
|
2750
|
+
baseline: baseline("slack", {
|
|
2751
|
+
operations: [...MESSAGE_CORE, "send.interactive", "send.reaction", "edit.message", "delete.message", "command.register", "channel.post", "thread.reply"],
|
|
2752
|
+
direction: { inbound: true, outbound: true },
|
|
2753
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "any channel the bot is in; DMs" },
|
|
2754
|
+
windows: [],
|
|
2755
|
+
templates: { required: "never", reviewed: false },
|
|
2756
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2757
|
+
messageTypes: ["text", "image", "audio", "video", "document", "reaction", "interactive", "card"],
|
|
2758
|
+
limits: { textChars: 4e3, note: "truncated at 40,000 (doc 01 \xA73)" },
|
|
2759
|
+
receipts: [],
|
|
2760
|
+
edits: { edit: true, delete: true, reactions: true, commands: true },
|
|
2761
|
+
transport: ["webhook", "gateway"],
|
|
2762
|
+
identity: { peerIdKind: "slack_user", peerIdScope: "workspace_scoped" },
|
|
2763
|
+
readinessGates: CONTAINER_GATES
|
|
2764
|
+
})
|
|
2765
|
+
},
|
|
2766
|
+
{
|
|
2767
|
+
id: "teams",
|
|
2768
|
+
displayName: "Microsoft Teams",
|
|
2769
|
+
addressSpace: "app",
|
|
2770
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2771
|
+
bindingCapabilities: ["teams.message"],
|
|
2772
|
+
baseline: baseline("teams", {
|
|
2773
|
+
operations: [...MESSAGE_CORE, "send.interactive", "edit.message", "delete.message", "channel.post", "thread.reply", "read.receipt"],
|
|
2774
|
+
direction: { inbound: true, outbound: true },
|
|
2775
|
+
initiation: { agentInitiated: "installed_only", userInitiated: true, firstContactRule: "proactive send needs an installation in scope plus a conversation reference" },
|
|
2776
|
+
windows: [],
|
|
2777
|
+
templates: { required: "never", reviewed: false },
|
|
2778
|
+
surfaces: ["direct", "group", "channel", "thread"],
|
|
2779
|
+
messageTypes: ["text", "image", "document", "card", "interactive", "reaction"],
|
|
2780
|
+
limits: { textBytes: 102400, note: "message payload \u2264100 KB (doc 01 \xA73)" },
|
|
2781
|
+
receipts: ["read"],
|
|
2782
|
+
edits: { edit: true, delete: true, reactions: false, commands: false },
|
|
2783
|
+
transport: ["webhook"],
|
|
2784
|
+
identity: { peerIdKind: "teams_user", peerIdScope: "installation_scoped" },
|
|
2785
|
+
readinessGates: CONTAINER_GATES
|
|
2786
|
+
})
|
|
2787
|
+
},
|
|
2788
|
+
{
|
|
2789
|
+
id: "rcs",
|
|
2790
|
+
displayName: "RCS",
|
|
2791
|
+
addressSpace: "rcs",
|
|
2792
|
+
surfaces: ["direct"],
|
|
2793
|
+
bindingCapabilities: ["rcs.message"],
|
|
2794
|
+
baseline: baseline("rcs", {
|
|
2795
|
+
operations: [...MESSAGE_CORE, "send.interactive", "read.receipt", "typing.indicate", "delete.message"],
|
|
2796
|
+
direction: { inbound: true, outbound: true },
|
|
2797
|
+
initiation: { agentInitiated: "opt_in_required", userInitiated: true, firstContactRule: "agent-initiated only with consent per AUP; STOP / UNSUBSCRIBE brand-wide" },
|
|
2798
|
+
windows: [],
|
|
2799
|
+
templates: { required: "never", reviewed: false },
|
|
2800
|
+
surfaces: ["direct"],
|
|
2801
|
+
messageTypes: ["text", "image", "audio", "video", "document", "card", "carousel", "interactive"],
|
|
2802
|
+
limits: { textChars: 3072, mediaBytes: 104857600 },
|
|
2803
|
+
receipts: ["delivered", "read"],
|
|
2804
|
+
edits: { edit: false, delete: true, reactions: false, commands: false },
|
|
2805
|
+
transport: ["webhook"],
|
|
2806
|
+
identity: { peerIdKind: "phone", peerIdScope: "global" },
|
|
2807
|
+
registration: [{ kind: "agent_launch", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"] }],
|
|
2808
|
+
readinessGates: [
|
|
2809
|
+
{ condition: "CredentialsValid", class: "authority", blocks: "*", ttlSec: 300 },
|
|
2810
|
+
{ condition: "RegistrationApproved", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
|
|
2811
|
+
{ condition: "PeerReachable", class: "authority", blocks: ["send.text", "send.media", "send.interactive", "initiate.conversation"], ttlSec: 86400 },
|
|
2812
|
+
{ condition: "WebhookHealthy", class: "operational", blocks: ["receive.message"], ttlSec: 900, evidence: "runtime" }
|
|
2813
|
+
]
|
|
2814
|
+
})
|
|
2815
|
+
}
|
|
2816
|
+
];
|
|
2817
|
+
new Map(CHANNELS.map((c) => [c.id, c]));
|
|
2818
|
+
CHANNEL_IDS = new Set(CHANNELS.map((c) => c.id));
|
|
2819
|
+
}
|
|
2820
|
+
});
|
|
2821
|
+
|
|
2822
|
+
// ../contracts/src/channel-connector/operations.ts
|
|
2823
|
+
var OPERATION_MODES, OPERATIONS, OPERATION_IDS;
|
|
2824
|
+
var init_operations = __esm({
|
|
2825
|
+
"../contracts/src/channel-connector/operations.ts"() {
|
|
2826
|
+
OPERATION_MODES = ["message", "realtime"];
|
|
2827
|
+
OPERATIONS = [
|
|
2828
|
+
{ id: "receive.message", mode: "message", summary: "accept an inbound message" },
|
|
2829
|
+
{ id: "send.text", mode: "message", summary: "send a text message" },
|
|
2830
|
+
{ id: "send.media", mode: "message", summary: "send a message carrying a content item (image, audio, video, document)" },
|
|
2831
|
+
{ id: "send.template", mode: "message", summary: "send a reviewed template" },
|
|
2832
|
+
{ id: "send.interactive", mode: "message", summary: "send structured/interactive content" },
|
|
2833
|
+
{ id: "send.reaction", mode: "message", summary: "react to a message" },
|
|
2834
|
+
{ id: "edit.message", mode: "message", summary: "edit a sent message" },
|
|
2835
|
+
{ id: "delete.message", mode: "message", summary: "delete a sent message" },
|
|
2836
|
+
{ id: "initiate.conversation", mode: "message", summary: "first contact / outside any window" },
|
|
2837
|
+
{ id: "reply.in_window", mode: "message", summary: "reply inside an open window" },
|
|
2838
|
+
{ id: "call.receive", mode: "realtime", summary: "accept an inbound realtime session" },
|
|
2839
|
+
{ id: "call.place", mode: "realtime", summary: "place an outbound realtime session" },
|
|
2840
|
+
{ id: "read.receipt", mode: "message", summary: "mark a message as read" },
|
|
2841
|
+
{ id: "typing.indicate", mode: "message", summary: "typing indicator" },
|
|
2842
|
+
{ id: "command.register", mode: "message", summary: "register slash/bot commands" },
|
|
2843
|
+
{ id: "thread.reply", mode: "message", summary: "reply inside a thread" },
|
|
2844
|
+
{ id: "group.post", mode: "message", summary: "post to a group surface" },
|
|
2845
|
+
{ id: "channel.post", mode: "message", summary: "post to a channel surface" }
|
|
2846
|
+
];
|
|
2847
|
+
new Map(
|
|
2848
|
+
OPERATIONS.map((op) => [op.id, op])
|
|
2849
|
+
);
|
|
2850
|
+
OPERATION_IDS = new Set(OPERATIONS.map((op) => op.id));
|
|
2851
|
+
}
|
|
2852
|
+
});
|
|
2853
|
+
|
|
2854
|
+
// ../contracts/src/channel-connector/ids.ts
|
|
2855
|
+
var A2_SHIPPED_ROOTS, A2_PLATFORM_ROOTS, NAMESPACE_RE, SEMVER_RE;
|
|
2856
|
+
var init_ids2 = __esm({
|
|
2857
|
+
"../contracts/src/channel-connector/ids.ts"() {
|
|
2858
|
+
init_channels();
|
|
2859
|
+
init_operations();
|
|
2860
|
+
A2_SHIPPED_ROOTS = [
|
|
2861
|
+
"llm",
|
|
2862
|
+
"conversation",
|
|
2863
|
+
"channels",
|
|
2864
|
+
"tools",
|
|
2865
|
+
"guard",
|
|
2866
|
+
"store",
|
|
2867
|
+
"compliance",
|
|
2868
|
+
"carriers",
|
|
2869
|
+
"agents",
|
|
2870
|
+
"agentRun",
|
|
2871
|
+
"telemetry"
|
|
2872
|
+
];
|
|
2873
|
+
A2_PLATFORM_ROOTS = [
|
|
2874
|
+
...A2_SHIPPED_ROOTS,
|
|
2875
|
+
"policy",
|
|
2876
|
+
"identity",
|
|
2877
|
+
"binding",
|
|
2878
|
+
"provider",
|
|
2879
|
+
"workflow",
|
|
2880
|
+
"session",
|
|
2881
|
+
"engine",
|
|
2882
|
+
"message",
|
|
2883
|
+
"connector",
|
|
2884
|
+
"communication",
|
|
2885
|
+
"event",
|
|
2886
|
+
"reach",
|
|
2887
|
+
"handler",
|
|
2888
|
+
"plugin",
|
|
2889
|
+
"scope",
|
|
2890
|
+
"snapshot"
|
|
2891
|
+
];
|
|
2892
|
+
/* @__PURE__ */ new Set([
|
|
2893
|
+
...A2_PLATFORM_ROOTS,
|
|
2894
|
+
...[...OPERATION_IDS].map((id) => id.split(".")[0]),
|
|
2895
|
+
...CHANNEL_IDS
|
|
2896
|
+
]);
|
|
2897
|
+
NAMESPACE_RE = /^[a-z0-9]+\.[a-z0-9]+$/;
|
|
2898
|
+
SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/;
|
|
2899
|
+
}
|
|
2900
|
+
});
|
|
2901
|
+
function parseContentRef(ref) {
|
|
2902
|
+
const m = CONTENT_REF_RE.exec(ref);
|
|
2903
|
+
return m ? { principalId: m[1], contentId: m[2] } : null;
|
|
2904
|
+
}
|
|
2905
|
+
function isContentRef(value) {
|
|
2906
|
+
return typeof value === "string" && CONTENT_REF_RE.test(value);
|
|
2907
|
+
}
|
|
2908
|
+
function looksLikeProviderLocator(value) {
|
|
2909
|
+
return /^(https?:\/\/|s3:\/\/|gs:\/\/)/i.test(value) || value.includes("?") || value.includes("://");
|
|
2910
|
+
}
|
|
2911
|
+
var MODALITY_KINDS, STRUCTURED_KINDS, CONTENT_KINDS, LOSSINESS, CONTENT_REF_RE, MessageContentProvenance, noLocator, MessageContentMetadata, contentRefSchema, OriginalMessageContent, DerivedMessageContent;
|
|
2912
|
+
var init_content_kinds = __esm({
|
|
2913
|
+
"../contracts/src/channel-connector/content-kinds.ts"() {
|
|
2914
|
+
MODALITY_KINDS = ["text", "image", "audio", "video", "document"];
|
|
2915
|
+
STRUCTURED_KINDS = [
|
|
2916
|
+
"location",
|
|
2917
|
+
"contact",
|
|
2918
|
+
"sticker",
|
|
2919
|
+
"reaction",
|
|
2920
|
+
"interactive",
|
|
2921
|
+
"card",
|
|
2922
|
+
"carousel",
|
|
2923
|
+
"poll",
|
|
2924
|
+
"template"
|
|
2925
|
+
];
|
|
2926
|
+
CONTENT_KINDS = [...MODALITY_KINDS, ...STRUCTURED_KINDS];
|
|
2927
|
+
new Set(CONTENT_KINDS);
|
|
2928
|
+
LOSSINESS = ["lossless", "lossy", "semantic"];
|
|
2929
|
+
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})$/;
|
|
2930
|
+
MessageContentProvenance = z.object({
|
|
2931
|
+
/** The connector that produced the item (A7 id). */
|
|
2932
|
+
connector: z.string(),
|
|
2933
|
+
/** The provider-native identifier — reconciliation only; never agent-facing. */
|
|
2934
|
+
providerRef: z.string().optional(),
|
|
2935
|
+
receivedAt: z.string().datetime().optional()
|
|
2936
|
+
}).strict();
|
|
2937
|
+
noLocator = (label) => z.string().refine((v) => !looksLikeProviderLocator(v), { message: `${label} must not carry a provider locator \u2014 provider ids live in provenance.providerRef only` });
|
|
2938
|
+
MessageContentMetadata = z.object({
|
|
2939
|
+
bytes: z.number().int().nonnegative().optional(),
|
|
2940
|
+
durationMs: z.number().int().nonnegative().optional(),
|
|
2941
|
+
width: z.number().int().positive().optional(),
|
|
2942
|
+
height: z.number().int().positive().optional(),
|
|
2943
|
+
filename: noLocator("metadata.filename").optional(),
|
|
2944
|
+
caption: noLocator("metadata.caption").optional(),
|
|
2945
|
+
/** e.g. `voice_note` for audio. */
|
|
2946
|
+
variant: z.string().regex(/^[a-z0-9_]+$/).optional(),
|
|
2947
|
+
language: z.string().max(16).optional()
|
|
2948
|
+
}).strict();
|
|
2949
|
+
contentRefSchema = z.string().refine(isContentRef, {
|
|
2950
|
+
message: "ref must be an opaque platform content reference `content:<principal>/<uuid>`"
|
|
2951
|
+
});
|
|
2952
|
+
OriginalMessageContent = z.object({
|
|
2953
|
+
kind: z.enum(CONTENT_KINDS),
|
|
2954
|
+
/** MIME type or registry format id. */
|
|
2955
|
+
format: z.string().min(1),
|
|
2956
|
+
ref: contentRefSchema,
|
|
2957
|
+
metadata: MessageContentMetadata.default({}),
|
|
2958
|
+
provenance: MessageContentProvenance,
|
|
2959
|
+
/** Inline text for `text` kind only (the ref still names the stored item). */
|
|
2960
|
+
text: z.string().optional()
|
|
2961
|
+
}).strict().refine((item) => item.text === void 0 || item.kind === "text", { message: "inline text is only valid on kind=text" });
|
|
2962
|
+
DerivedMessageContent = OriginalMessageContent.innerType().extend({
|
|
2963
|
+
derivedFrom: contentRefSchema,
|
|
2964
|
+
/** The transform capability id (registry or extension id). */
|
|
2965
|
+
transform: z.string().min(1),
|
|
2966
|
+
/** A7 actor ref of the producer. */
|
|
2967
|
+
producer: z.string().min(1),
|
|
2968
|
+
lossiness: z.enum(LOSSINESS)
|
|
2969
|
+
}).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, {
|
|
2970
|
+
message: "a derived item must derive from content of the same principal"
|
|
2971
|
+
});
|
|
2972
|
+
z.union([DerivedMessageContent, OriginalMessageContent]);
|
|
2973
|
+
}
|
|
2974
|
+
});
|
|
2975
|
+
var STEP_KINDS, OWNERSHIP_MODES, WorkflowStep, ConnectorWorkflow;
|
|
2976
|
+
var init_onboarding = __esm({
|
|
2977
|
+
"../contracts/src/channel-connector/onboarding.ts"() {
|
|
2978
|
+
STEP_KINDS = [
|
|
2979
|
+
"FORM",
|
|
2980
|
+
"SECRET",
|
|
2981
|
+
"OAUTH",
|
|
2982
|
+
"APP_INSTALL",
|
|
2983
|
+
"RESOURCE_SELECT",
|
|
2984
|
+
"RESOURCE_CREATE",
|
|
2985
|
+
"REGISTER",
|
|
2986
|
+
"SUBMIT_APPROVAL",
|
|
2987
|
+
"WAIT",
|
|
2988
|
+
"ACTION_REQUIRED",
|
|
2989
|
+
"GATEWAY_CONNECT",
|
|
2990
|
+
"PROBE",
|
|
2991
|
+
"ACTIVATE"
|
|
2992
|
+
];
|
|
2993
|
+
OWNERSHIP_MODES = ["platform_managed", "customer_owned"];
|
|
2994
|
+
new Set(OWNERSHIP_MODES);
|
|
2995
|
+
new Set(STEP_KINDS);
|
|
2996
|
+
WorkflowStep = z.object({
|
|
2997
|
+
id: z.string().min(1),
|
|
2998
|
+
kind: z.enum(STEP_KINDS),
|
|
2999
|
+
title: z.string().min(1),
|
|
3000
|
+
/** JSON Schema for FORM/SECRET inputs — schema-driven, no connector UI code. */
|
|
3001
|
+
schema: z.record(z.unknown()).optional(),
|
|
3002
|
+
/** Credential role for SECRET/OAUTH outputs. */
|
|
3003
|
+
role: z.string().optional(),
|
|
3004
|
+
approvalKind: z.string().optional(),
|
|
3005
|
+
resourceKind: z.string().optional(),
|
|
3006
|
+
mode: z.string().optional(),
|
|
3007
|
+
dependsOn: z.array(z.string()).default([]),
|
|
3008
|
+
optional: z.boolean().default(false),
|
|
3009
|
+
/**
|
|
3010
|
+
* Ownership modes this step applies to (absent = all). A REQUIRED step out of the run's mode is ownership-skipped
|
|
3011
|
+
* (`OUT_OF_MODE`), which is distinct from `optional` — the engine must skip it regardless of `optional` (#306 §9).
|
|
3012
|
+
*/
|
|
3013
|
+
ownership: z.array(z.enum(OWNERSHIP_MODES)).optional(),
|
|
3014
|
+
repeatable: z.boolean().default(false),
|
|
3015
|
+
producesChannels: z.array(z.string()).default([])
|
|
3016
|
+
}).strict();
|
|
3017
|
+
ConnectorWorkflow = z.object({
|
|
3018
|
+
version: z.string().min(1),
|
|
3019
|
+
steps: z.array(WorkflowStep).min(1),
|
|
3020
|
+
/** What ACTIVATE requires (condition type ids). */
|
|
3021
|
+
activationGates: z.array(z.string()),
|
|
3022
|
+
/** The single step that re-establishes credentials. */
|
|
3023
|
+
reauth: z.object({ stepId: z.string() }).strict(),
|
|
3024
|
+
/** Steps that adopt existing resources on transfer / reconnect. */
|
|
3025
|
+
relink: z.object({ stepIds: z.array(z.string()) }).strict()
|
|
3026
|
+
}).strict();
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
var EXCLUSIVITY, TIMEOUT_EFFECTS, RECONCILE_BY, ABSENCE_PROOF_METHODS, DUPLICATE_COSTS, RESOURCE_KIND_RE, ResourceKindDeclaration;
|
|
3030
|
+
var init_external_resources = __esm({
|
|
3031
|
+
"../contracts/src/channel-connector/external-resources.ts"() {
|
|
3032
|
+
init_ids2();
|
|
3033
|
+
EXCLUSIVITY = ["exclusive", "per_connection", "shared"];
|
|
3034
|
+
TIMEOUT_EFFECTS = ["UNKNOWN", "NO_EFFECT", "PROVIDER_ACCEPTS", "PROVIDER_REJECTS"];
|
|
3035
|
+
RECONCILE_BY = ["provider_ref", "correlation_tag", "listing", "none"];
|
|
3036
|
+
ABSENCE_PROOF_METHODS = ["provider_ref", "correlation_tag", "listing"];
|
|
3037
|
+
DUPLICATE_COSTS = ["low", "high"];
|
|
3038
|
+
RESOURCE_KIND_RE = /^[a-z0-9]+(\.[a-z0-9_]+)+$/;
|
|
3039
|
+
ResourceKindDeclaration = z.object({
|
|
3040
|
+
kind: z.string().regex(RESOURCE_KIND_RE, "resource kind must be a namespaced dotted id"),
|
|
3041
|
+
/** Customer-facing name of the kind (presentation only; the connector owns its provider vocabulary). */
|
|
3042
|
+
label: z.string().min(1).optional(),
|
|
3043
|
+
parentKinds: z.array(z.string()).default([]),
|
|
3044
|
+
/** Channels this kind enables (registry ids), if any. */
|
|
3045
|
+
channels: z.array(z.string()).default([]),
|
|
3046
|
+
/** An installation target (guild, workspace, tenant, business account). */
|
|
3047
|
+
isContainer: z.boolean().default(false),
|
|
3048
|
+
/** A channel identity (sender). */
|
|
3049
|
+
isSender: z.boolean().default(false),
|
|
3050
|
+
isRegulatory: z.boolean().default(false),
|
|
3051
|
+
exclusivity: z.enum(EXCLUSIVITY),
|
|
3052
|
+
/** Provider-side sharing cap, manifest DATA never a core constant (e.g. Meta WABA = 2). */
|
|
3053
|
+
maxHolders: z.number().int().positive().optional(),
|
|
3054
|
+
/** doc 06 `reconcile { by, absenceProof?, duplicateCost }`. */
|
|
3055
|
+
reconcile: z.object({
|
|
3056
|
+
by: z.enum(RECONCILE_BY),
|
|
3057
|
+
absenceProof: z.object({ minAgeSec: z.number().int().nonnegative(), method: z.enum(ABSENCE_PROOF_METHODS) }).strict().optional(),
|
|
3058
|
+
duplicateCost: z.enum(DUPLICATE_COSTS)
|
|
3059
|
+
}).strict(),
|
|
3060
|
+
/** doc 06 `release { destructive }`. */
|
|
3061
|
+
release: z.object({ destructive: z.boolean() }).strict().default({ destructive: false }),
|
|
3062
|
+
/** Timeout semantics for the effect that creates this kind (D-23). */
|
|
3063
|
+
createTimeoutEffect: z.enum(TIMEOUT_EFFECTS).default("UNKNOWN")
|
|
3064
|
+
}).strict().superRefine((decl, ctx) => {
|
|
3065
|
+
if (decl.isContainer && decl.exclusivity === "exclusive") {
|
|
3066
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `container kind ${decl.kind} cannot be exclusive` });
|
|
3067
|
+
}
|
|
3068
|
+
if (decl.maxHolders !== void 0 && decl.exclusivity === "exclusive" && decl.maxHolders !== 1) {
|
|
3069
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `exclusive kind ${decl.kind} cannot declare maxHolders > 1` });
|
|
3070
|
+
}
|
|
3071
|
+
if (decl.exclusivity === "shared" && (decl.isSender || decl.isContainer)) {
|
|
3072
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `shared kind ${decl.kind} is referenced by value and cannot be a sender or container` });
|
|
3073
|
+
}
|
|
3074
|
+
if (decl.reconcile.absenceProof && decl.reconcile.by === "none") {
|
|
3075
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `kind ${decl.kind}: absenceProof needs a reconcile method other than none` });
|
|
3076
|
+
}
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
|
|
3081
|
+
// ../contracts/src/channel-connector/approvals.ts
|
|
3082
|
+
var APPROVAL_KINDS;
|
|
3083
|
+
var init_approvals = __esm({
|
|
3084
|
+
"../contracts/src/channel-connector/approvals.ts"() {
|
|
3085
|
+
APPROVAL_KINDS = [
|
|
3086
|
+
"business_verification",
|
|
3087
|
+
"campaign_vetting",
|
|
3088
|
+
"toll_free_verification",
|
|
3089
|
+
"app_review",
|
|
3090
|
+
"privileged_permission_review",
|
|
3091
|
+
"admin_consent",
|
|
3092
|
+
"display_name_review",
|
|
3093
|
+
"template_review",
|
|
3094
|
+
"agent_launch",
|
|
3095
|
+
"brand_verification"
|
|
3096
|
+
];
|
|
3097
|
+
new Set(APPROVAL_KINDS);
|
|
3098
|
+
}
|
|
3099
|
+
});
|
|
3100
|
+
var DEPLOYMENT_MODELS, CREDENTIAL_STRENGTHS, CREDENTIAL_ROLES, INSTALLATION_KINDS, AUTHORIZATION_METHODS, AUTHORIZATION_CAPABILITIES, AuthorizationMethodDeclaration, INBOUND_AUTH_AVAILABILITY, InboundAuthMethodDeclaration, MutationApprovalDeclaration, ReadinessGateSchema, FormatsSchema, LimitsSchema, DescriptorOverride, DecisionRequestDeclaration;
|
|
3101
|
+
var init_manifest = __esm({
|
|
3102
|
+
"../contracts/src/channel-connector/manifest.ts"() {
|
|
3103
|
+
init_identity();
|
|
3104
|
+
init_approvals();
|
|
3105
|
+
init_capability_state();
|
|
3106
|
+
init_channels();
|
|
3107
|
+
init_content_kinds();
|
|
3108
|
+
init_external_resources();
|
|
3109
|
+
init_ids2();
|
|
3110
|
+
init_onboarding();
|
|
3111
|
+
init_operations();
|
|
3112
|
+
init_onboarding();
|
|
3113
|
+
DEPLOYMENT_MODELS = ["per_tenant_app", "platform_app", "both"];
|
|
3114
|
+
CREDENTIAL_STRENGTHS = ["ephemeral", "brokered", "exported_static"];
|
|
3115
|
+
CREDENTIAL_ROLES = ["primary", "signing", "webhook", "refresh", "app_signing", "oauth_client", "partner"];
|
|
3116
|
+
INSTALLATION_KINDS = ["guild", "user", "workspace", "org", "tenant", "team", "business_account", "chat"];
|
|
3117
|
+
new Set(INSTALLATION_KINDS);
|
|
3118
|
+
z.string().regex(/^cred:[a-z0-9_-]+\/[^\s]+$/, "A5 credential ref `cred:<store>/<path>`");
|
|
3119
|
+
AUTHORIZATION_METHODS = ["oauth", "api_key", "api_secret", "account_token", "bot_token", "delegated_app", "installation_token", "certificate", "other"];
|
|
3120
|
+
AUTHORIZATION_CAPABILITIES = ["discover", "provision", "send", "receive", "manage_resources", "manage_compliance", "manage_webhooks", "manage_installations", "manage_accounts"];
|
|
3121
|
+
AuthorizationMethodDeclaration = z.object({
|
|
3122
|
+
id: z.string().min(1),
|
|
3123
|
+
method: z.enum(AUTHORIZATION_METHODS),
|
|
3124
|
+
credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).min(1),
|
|
3125
|
+
capabilities: z.array(z.enum(AUTHORIZATION_CAPABILITIES)).default([]),
|
|
3126
|
+
scope: z.enum(["account", "container"]).optional()
|
|
3127
|
+
}).strict();
|
|
3128
|
+
INBOUND_AUTH_AVAILABILITY = ["ga", "beta", "private_beta", "unavailable"];
|
|
3129
|
+
InboundAuthMethodDeclaration = z.object({
|
|
3130
|
+
id: z.string().min(1),
|
|
3131
|
+
verifier: z.string().min(1),
|
|
3132
|
+
credentialRole: z.enum(CREDENTIAL_ROLES),
|
|
3133
|
+
compatibleWith: z.array(z.object({ authMethodId: z.string().min(1), reusesAuthCredential: z.boolean() }).strict()).default([]),
|
|
3134
|
+
availability: z.enum(INBOUND_AUTH_AVAILABILITY).default("ga")
|
|
3135
|
+
}).strict();
|
|
3136
|
+
MutationApprovalDeclaration = z.object({
|
|
3137
|
+
effect: z.string().min(1),
|
|
3138
|
+
approval: z.enum(["explicit", "implicit"]),
|
|
3139
|
+
capturePriorState: z.boolean().default(false)
|
|
3140
|
+
}).strict();
|
|
3141
|
+
ReadinessGateSchema = z.object({
|
|
3142
|
+
condition: z.string().min(1),
|
|
3143
|
+
class: z.enum(CONDITION_CLASSES),
|
|
3144
|
+
blocks: z.union([z.literal("*"), z.array(z.string())]),
|
|
3145
|
+
ttlSec: z.number().int().nonnegative(),
|
|
3146
|
+
evidence: z.enum(["configuration", "runtime"]).optional()
|
|
3147
|
+
}).strict();
|
|
3148
|
+
FormatsSchema = z.record(
|
|
3149
|
+
z.enum(CONTENT_KINDS),
|
|
3150
|
+
z.object({ mime: z.array(z.string()).optional(), variants: z.array(z.string()).optional() }).strict()
|
|
3151
|
+
);
|
|
3152
|
+
LimitsSchema = z.object({
|
|
3153
|
+
textChars: z.number().int().positive().optional(),
|
|
3154
|
+
textBytes: z.number().int().positive().optional(),
|
|
3155
|
+
mediaBytes: z.number().int().positive().optional(),
|
|
3156
|
+
segments: z.boolean().optional(),
|
|
3157
|
+
perSecond: z.number().positive().optional(),
|
|
3158
|
+
perRecipientPerSecond: z.number().positive().optional(),
|
|
3159
|
+
formats: FormatsSchema.optional(),
|
|
3160
|
+
note: z.string().optional()
|
|
3161
|
+
}).strict();
|
|
3162
|
+
DescriptorOverride = z.object({
|
|
3163
|
+
version: z.string().min(1).optional(),
|
|
3164
|
+
operations: z.array(z.string()).optional(),
|
|
3165
|
+
direction: z.object({ inbound: z.boolean(), outbound: z.boolean() }).strict().optional(),
|
|
3166
|
+
initiation: z.object({ agentInitiated: z.enum(AGENT_INITIATION), userInitiated: z.boolean(), firstContactRule: z.string() }).strict().optional(),
|
|
3167
|
+
windows: z.array(
|
|
3168
|
+
z.object({
|
|
3169
|
+
kind: z.literal("reply"),
|
|
3170
|
+
durationSec: z.number().int().positive(),
|
|
3171
|
+
opensOn: z.enum(["inbound_message", "user_action"]),
|
|
3172
|
+
outside: z.enum(WINDOW_OUTSIDE)
|
|
3173
|
+
}).strict()
|
|
3174
|
+
).optional(),
|
|
3175
|
+
templates: z.object({ required: z.enum(TEMPLATE_REQUIREMENT), reviewed: z.boolean(), categories: z.array(z.string()).optional() }).strict().optional(),
|
|
3176
|
+
surfaces: z.array(z.enum(SURFACES)).optional(),
|
|
3177
|
+
messageTypes: z.array(z.enum(CONTENT_KINDS)).optional(),
|
|
3178
|
+
limits: LimitsSchema.optional(),
|
|
3179
|
+
receipts: z.array(z.enum(RECEIPT_KINDS)).optional(),
|
|
3180
|
+
edits: z.object({ edit: z.boolean(), delete: z.boolean(), reactions: z.boolean(), commands: z.boolean() }).strict().optional(),
|
|
3181
|
+
transport: z.array(z.enum(TRANSPORT_KINDS)).optional(),
|
|
3182
|
+
identity: z.object({ peerIdKind: z.string(), peerIdScope: z.enum(PEER_ID_SCOPES) }).strict().optional(),
|
|
3183
|
+
requiredPermissions: z.array(z.string()).optional(),
|
|
3184
|
+
registration: z.array(z.object({ kind: z.string(), blocks: z.array(z.string()) }).strict()).optional(),
|
|
3185
|
+
readinessGates: z.array(ReadinessGateSchema).optional()
|
|
3186
|
+
}).strict();
|
|
3187
|
+
DecisionRequestDeclaration = z.object({
|
|
3188
|
+
kind: z.string().min(1),
|
|
3189
|
+
responseDeadlineSec: z.number().int().positive(),
|
|
3190
|
+
/** The provider's law when we stay silent. */
|
|
3191
|
+
defaultOutcome: z.enum(TIMEOUT_EFFECTS)
|
|
3192
|
+
}).strict();
|
|
3193
|
+
z.object({
|
|
3194
|
+
/** A7 `<publisher>/<name>`. */
|
|
3195
|
+
id: z.string().min(3),
|
|
3196
|
+
/** A7: semver. */
|
|
3197
|
+
version: z.string().regex(SEMVER_RE, "A7: plugin versions are semver"),
|
|
3198
|
+
displayName: z.string().min(1),
|
|
3199
|
+
publisher: z.string().min(1),
|
|
3200
|
+
/** A STABLE https URL of the provider's own console (the `provider_console` remediation carries it); absent ⇒ remediation has no link. */
|
|
3201
|
+
consoleUrl: z.string().url().optional(),
|
|
3202
|
+
/** 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. */
|
|
3203
|
+
namespace: z.string().regex(NAMESPACE_RE, "namespace must be <root>.<label>").optional(),
|
|
3204
|
+
deployment: z.enum(DEPLOYMENT_MODELS),
|
|
3205
|
+
credentialStrength: z.enum(CREDENTIAL_STRENGTHS),
|
|
3206
|
+
/** Roles this connector's connections store refs under. */
|
|
3207
|
+
credentialRoles: z.array(z.enum(CREDENTIAL_ROLES)).default(["primary"]),
|
|
3208
|
+
/** Channels served, each with its descriptor diff over the baseline. */
|
|
3209
|
+
channels: z.array(z.object({ channel: z.string().min(1), descriptor: DescriptorOverride.default({}) }).strict()).min(1),
|
|
3210
|
+
/** Operations the connector implements (registry ids or its own extension ops). */
|
|
3211
|
+
operations: z.array(z.string()).min(1),
|
|
3212
|
+
/** Extension operations this connector defines — each MUST declare a mode. */
|
|
3213
|
+
extensionOperations: z.array(z.object({ id: z.string(), mode: z.enum(OPERATION_MODES), summary: z.string() }).strict()).default([]),
|
|
3214
|
+
onboarding: ConnectorWorkflow,
|
|
3215
|
+
resourceKinds: z.array(ResourceKindDeclaration).default([]),
|
|
3216
|
+
installationKinds: z.array(z.string()).default([]),
|
|
3217
|
+
transports: z.array(z.enum(TRANSPORT_KINDS)).min(1),
|
|
3218
|
+
/** doc 05 §2: transports the provider makes mutually exclusive (e.g. webhook vs long_poll). */
|
|
3219
|
+
transportsExclusive: z.array(z.array(z.enum(TRANSPORT_KINDS)).min(2)).default([]),
|
|
3220
|
+
/** Verifier id the ingress runs before parsing; required with the `webhook` transport. */
|
|
3221
|
+
webhookVerifier: z.string().min(1).optional(),
|
|
3222
|
+
/** Reconcile handler ids by resource kind (doc 02). */
|
|
3223
|
+
reconcilers: z.array(z.object({ resourceKind: z.string(), handler: z.string().min(1) }).strict()).default([]),
|
|
3224
|
+
/** Inbound decision requests with provider-defined defaults (D-23). */
|
|
3225
|
+
decisionRequests: z.array(DecisionRequestDeclaration).default([]),
|
|
3226
|
+
/** Identifier kinds contributed to the ch. 00 B-1 vocabulary. */
|
|
3227
|
+
identifierKinds: z.array(z.string()).default([]),
|
|
3228
|
+
/** Approval kinds used (registry or extension ids). */
|
|
3229
|
+
approvalKinds: z.array(z.string()).default([]),
|
|
3230
|
+
/** Attribution key names used under `platform_app` (doc 08 §4a step 3). */
|
|
3231
|
+
attributionKeys: z.array(z.string()).default([]),
|
|
3232
|
+
/** Quarantine window for PENDING_ATTRIBUTION (D-21), seconds. */
|
|
3233
|
+
pendingAttributionTtlSec: z.number().int().positive().default(7 * 86400),
|
|
3234
|
+
metadata: z.record(z.unknown()).default({}),
|
|
3235
|
+
/** 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. */
|
|
3236
|
+
ownershipModes: z.array(z.enum(OWNERSHIP_MODES)).min(1).default(["customer_owned"]),
|
|
3237
|
+
/** Authorization methods the connector supports (#306 §4/C3). */
|
|
3238
|
+
authorizationMethods: z.array(AuthorizationMethodDeclaration).default([]),
|
|
3239
|
+
/** Inbound-event authentication strategies (#306 §4/C4); `webhookVerifier` is the single-strategy shorthand, normalized by `resolveInboundAuthMethods`. */
|
|
3240
|
+
inboundAuthMethods: z.array(InboundAuthMethodDeclaration).default([]),
|
|
3241
|
+
/** Provider effects that require an explicit decision, keyed on stable effect ids (#306 L5/C5). */
|
|
3242
|
+
mutationApprovals: z.array(MutationApprovalDeclaration).default([])
|
|
3243
|
+
}).strict();
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
// ../contracts/src/channel-connector/index.ts
|
|
3248
|
+
var init_channel_connector = __esm({
|
|
3249
|
+
"../contracts/src/channel-connector/index.ts"() {
|
|
3250
|
+
init_ids2();
|
|
3251
|
+
init_operations();
|
|
3252
|
+
init_content_kinds();
|
|
3253
|
+
init_capability_state();
|
|
3254
|
+
init_onboarding();
|
|
3255
|
+
init_external_resources();
|
|
3256
|
+
init_approvals();
|
|
3257
|
+
init_channels();
|
|
3258
|
+
init_manifest();
|
|
3259
|
+
}
|
|
3260
|
+
});
|
|
3261
|
+
function isSafeMonitoringHref(href) {
|
|
3262
|
+
if (href !== href.trim() || UNSAFE_CHARS.test(href) || href.includes("\\") || /%2e|%2f|%5c|%25/i.test(href)) return false;
|
|
3263
|
+
if (href.startsWith("/")) {
|
|
3264
|
+
if (href.startsWith("//")) return false;
|
|
3265
|
+
try {
|
|
3266
|
+
const u = new URL(href, "https://dashboard.invalid");
|
|
3267
|
+
return u.origin === "https://dashboard.invalid" && u.pathname.startsWith("/dashboard/") && u.pathname + u.search + u.hash === href;
|
|
3268
|
+
} catch {
|
|
3269
|
+
return false;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
if (!href.startsWith("https://")) return false;
|
|
3273
|
+
try {
|
|
3274
|
+
const u = new URL(href);
|
|
3275
|
+
return u.protocol === "https:" && !u.username && !u.password && Boolean(u.hostname);
|
|
3276
|
+
} catch {
|
|
3277
|
+
return false;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
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;
|
|
3281
|
+
var init_monitoring = __esm({
|
|
3282
|
+
"../contracts/src/monitoring.ts"() {
|
|
3283
|
+
Label = z.string().trim().min(1).max(60);
|
|
3284
|
+
Short = z.string().max(300);
|
|
3285
|
+
Body = z.string().max(4e3);
|
|
3286
|
+
Iso = z.string().datetime();
|
|
3287
|
+
MonitoringAuthor = z.enum(["peer", "agent", "system"]);
|
|
3288
|
+
MonitoringTone = z.enum(["ok", "warn", "bad", "info", "neutral"]);
|
|
3289
|
+
UNSAFE_CHARS = /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2064\u2066-\u2069\ufeff\u{e0000}-\u{e007f}]/u;
|
|
3290
|
+
new RegExp(UNSAFE_CHARS.source, "gu");
|
|
3291
|
+
MonitoringBlock = z.discriminatedUnion("type", [
|
|
3292
|
+
/** One line of the conversation: what was said, by whom. */
|
|
3293
|
+
z.object({ type: z.literal("message"), author: MonitoringAuthor, text: Body, at: Iso.optional() }).strict(),
|
|
3294
|
+
/** Channel facts worth seeing at a glance (Discord server/channel, SMS from/to, call duration). */
|
|
3295
|
+
z.object({ type: z.literal("facts"), items: z.array(z.object({ label: Label, value: Short }).strict()).min(1).max(12) }).strict(),
|
|
3296
|
+
/** The communication's state in words, with a tone. */
|
|
3297
|
+
z.object({ type: z.literal("status"), label: Label, tone: MonitoringTone }).strict(),
|
|
3298
|
+
/** Somewhere to go: the provider's own view of it (https) or a deeper dashboard page. */
|
|
3299
|
+
z.object({ type: z.literal("link"), label: Label, href: z.string().max(2e3).refine(isSafeMonitoringHref, "href must be https or a /dashboard/ path") }).strict(),
|
|
3300
|
+
/** A dated lifecycle fact (sent, delivered, answered, ended). */
|
|
3301
|
+
z.object({ type: z.literal("event"), label: z.string().trim().min(1).max(120), at: Iso }).strict(),
|
|
3302
|
+
/** A spoken exchange, turn by turn. */
|
|
3303
|
+
z.object({
|
|
3304
|
+
type: z.literal("transcript"),
|
|
3305
|
+
turns: z.array(z.object({ author: MonitoringAuthor, text: Body, offsetMs: z.number().int().nonnegative().optional() }).strict()).max(500)
|
|
3306
|
+
}).strict()
|
|
3307
|
+
]);
|
|
3308
|
+
PresentedCommunication = z.object({
|
|
3309
|
+
/** One line for lists ("Asked: What is 2 + 2?"). */
|
|
3310
|
+
summary: z.string().max(140).optional(),
|
|
3311
|
+
/**
|
|
3312
|
+
* Something new starts at this communication — e.g. the agent began a fresh session with no memory of what came
|
|
3313
|
+
* before. The page draws a labelled divider before it. Additive to v1: a reader that ignores it loses nothing else.
|
|
3314
|
+
*/
|
|
3315
|
+
boundary: z.object({ label: z.string().trim().min(1).max(80) }).strict().optional(),
|
|
3316
|
+
blocks: z.array(MonitoringBlock).max(50)
|
|
3317
|
+
}).strict();
|
|
3318
|
+
MonitoringChannelId = z.string().regex(/^[a-z][a-z0-9_.-]{0,39}$/);
|
|
3319
|
+
MonitoringChannelRef = z.object({ id: z.string(), label: z.string() }).strict();
|
|
3320
|
+
MonitoredPeer = z.object({
|
|
3321
|
+
kind: z.string().nullable(),
|
|
3322
|
+
address: z.string().nullable(),
|
|
3323
|
+
/** From the channel's presenter (`describePeer`); null when it has no better name than the address. */
|
|
3324
|
+
displayName: z.string().nullable()
|
|
3325
|
+
}).strict();
|
|
3326
|
+
MonitoredConversationSummary = z.object({
|
|
3327
|
+
id: z.string().uuid(),
|
|
3328
|
+
status: z.string(),
|
|
3329
|
+
openedAt: z.string(),
|
|
3330
|
+
lastActivityAt: z.string(),
|
|
3331
|
+
agent: z.object({ id: z.string(), name: z.string() }).strict().nullable(),
|
|
3332
|
+
peer: MonitoredPeer,
|
|
3333
|
+
channels: z.array(MonitoringChannelRef),
|
|
3334
|
+
communications: z.number().int().nonnegative(),
|
|
3335
|
+
/** The latest INBOUND communication's one-liner (else the latest), as its channel presents it. */
|
|
3336
|
+
summary: z.string().nullable(),
|
|
3337
|
+
/** Something in it failed (a message not delivered, a call that failed). */
|
|
3338
|
+
failed: z.boolean(),
|
|
3339
|
+
/** A call in it is still in progress. */
|
|
3340
|
+
live: z.boolean()
|
|
3341
|
+
}).strict();
|
|
3342
|
+
MonitoredCommunication = z.object({
|
|
3343
|
+
id: z.string().uuid(),
|
|
3344
|
+
type: z.string(),
|
|
3345
|
+
channel: MonitoringChannelRef,
|
|
3346
|
+
direction: z.string(),
|
|
3347
|
+
status: z.string(),
|
|
3348
|
+
startedAt: z.string(),
|
|
3349
|
+
endedAt: z.string().nullable(),
|
|
3350
|
+
/** Which presenter shaped it (`vl/discord`, `vl/sms`, `generic`), for support. */
|
|
3351
|
+
presenter: z.string(),
|
|
3352
|
+
presented: PresentedCommunication
|
|
3353
|
+
}).strict();
|
|
3354
|
+
MonitoringUnlinkedHistory = z.object({ calls: z.boolean(), messages: z.boolean() }).strict();
|
|
3355
|
+
z.object({
|
|
3356
|
+
blocksVersion: z.number().int(),
|
|
3357
|
+
scope: z.string(),
|
|
3358
|
+
conversations: z.array(MonitoredConversationSummary),
|
|
3359
|
+
/** Opaque; pass back as `cursor` for the next (older) page. Null on the last page. */
|
|
3360
|
+
nextCursor: z.string().nullable(),
|
|
3361
|
+
/** History from before a channel was linked to the ledger (not shown here, 🔒 control-plane F8/F12). */
|
|
3362
|
+
unlinkedHistory: MonitoringUnlinkedHistory
|
|
3363
|
+
}).strict();
|
|
3364
|
+
z.object({
|
|
3365
|
+
blocksVersion: z.number().int(),
|
|
3366
|
+
scope: z.string(),
|
|
3367
|
+
conversation: MonitoredConversationSummary,
|
|
3368
|
+
/** Oldest first. The page holds the LATEST communications; `earlierCursor` fetches the ones before them. */
|
|
3369
|
+
communications: z.array(MonitoredCommunication),
|
|
3370
|
+
earlierCursor: z.string().nullable()
|
|
3371
|
+
}).strict();
|
|
3372
|
+
z.object({
|
|
3373
|
+
scope: z.string(),
|
|
3374
|
+
/** Channels a presenter currently shows, named by that presenter. */
|
|
3375
|
+
channels: z.array(MonitoringChannelRef),
|
|
3376
|
+
/** The Lines (numbers, bots) conversations in this scope arrived on. */
|
|
3377
|
+
lines: z.array(z.object({ id: z.string().uuid(), label: z.string() }).strict())
|
|
3378
|
+
}).strict();
|
|
3379
|
+
MONITORING_DIRECTIONS = ["inbound", "outbound"];
|
|
3380
|
+
MONITORING_STATES = ["live", "failed", "ok"];
|
|
3381
|
+
MONITORING_MAX_ID_FILTERS = 50;
|
|
3382
|
+
MONITORING_MAX_CHANNEL_FILTERS = 20;
|
|
3383
|
+
z.object({
|
|
3384
|
+
scope: z.enum(["env:dev", "env:stg", "env:prd"]).optional(),
|
|
3385
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
3386
|
+
cursor: z.string().max(200).optional(),
|
|
3387
|
+
from: z.string().datetime().optional(),
|
|
3388
|
+
to: z.string().datetime().optional(),
|
|
3389
|
+
channel: z.array(MonitoringChannelId).max(MONITORING_MAX_CHANNEL_FILTERS).optional(),
|
|
3390
|
+
direction: z.array(z.enum(MONITORING_DIRECTIONS)).max(2).optional(),
|
|
3391
|
+
line: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
|
|
3392
|
+
agent: z.array(z.string().uuid()).max(MONITORING_MAX_ID_FILTERS).optional(),
|
|
3393
|
+
status: z.array(z.enum(MONITORING_STATES)).max(3).optional(),
|
|
3394
|
+
customer: z.string().trim().max(100).optional()
|
|
3395
|
+
}).strict();
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
var WalletRefusalCode, WalletMode, WalletInvoiceStatus, WalletInvoice, WalletEntryKind, WalletEntry, TOPUP_MIN_USD, TOPUP_MAX_USD;
|
|
3399
|
+
var init_wallet = __esm({
|
|
3400
|
+
"../contracts/src/wallet.ts"() {
|
|
3401
|
+
WalletRefusalCode = z.enum([
|
|
3402
|
+
// Prepaid balance (or an invoiced credit limit) cannot cover the action.
|
|
3403
|
+
"insufficient_balance",
|
|
3404
|
+
// The wallet is frozen after a card dispute, until an operator unfreezes it.
|
|
3405
|
+
"account_frozen",
|
|
3406
|
+
// An invoice is unpaid past its 7 days' grace (B-19): new calls, messages and answers are refused; inbound calls are
|
|
3407
|
+
// still answered. Paying the invoice restores the workspace.
|
|
3408
|
+
"account_suspended",
|
|
3409
|
+
// The action's reservation already closed (e.g. a call ended or expired) — it cannot be re-admitted.
|
|
3410
|
+
"hold_closed"
|
|
3411
|
+
]);
|
|
3412
|
+
z.object({
|
|
3413
|
+
error: WalletRefusalCode,
|
|
3414
|
+
code: WalletRefusalCode,
|
|
3415
|
+
availableUsd: z.string(),
|
|
3416
|
+
needUsd: z.string(),
|
|
3417
|
+
message: z.string()
|
|
3418
|
+
});
|
|
3419
|
+
WalletMode = z.enum(["prepaid", "invoiced"]);
|
|
3420
|
+
z.object({
|
|
3421
|
+
mode: WalletMode,
|
|
3422
|
+
status: z.enum(["active", "frozen"]),
|
|
3423
|
+
// Decimal strings, six places (the ledger's micro-dollar precision).
|
|
3424
|
+
balanceUsd: z.string(),
|
|
3425
|
+
// Reserved for calls in progress.
|
|
3426
|
+
heldUsd: z.string(),
|
|
3427
|
+
// balance − held (+ credit limit when invoiced); null = unlimited.
|
|
3428
|
+
availableUsd: z.string().nullable(),
|
|
3429
|
+
creditLimitUsd: z.string().nullable(),
|
|
3430
|
+
// A card saved by the first top-up — what auto top-up charges.
|
|
3431
|
+
cardOnFile: z.boolean(),
|
|
3432
|
+
autoTopup: z.object({ enabled: z.boolean(), thresholdUsd: z.string(), amountUsd: z.string() }),
|
|
3433
|
+
// B-19: 'past_due' = an invoice is overdue (a pay-now banner); 'suspended' = paused for it (or by an operator).
|
|
3434
|
+
standing: z.enum(["good", "past_due", "suspended"]),
|
|
3435
|
+
// Why it is suspended: an invoice unpaid past grace (paying restores it) or an operator (only support lifts it).
|
|
3436
|
+
suspension: z.enum(["unpaid_invoice", "operator"]).nullable(),
|
|
3437
|
+
// The oldest overdue invoice, when there is one.
|
|
3438
|
+
overdueInvoice: z.object({ amountUsd: z.string(), dueAt: z.string(), payUrl: z.string().nullable() }).nullable(),
|
|
3439
|
+
// Invoiced wallets: what the next invoice holds so far (charges since the last invoice); null when prepaid.
|
|
3440
|
+
uninvoicedUsd: z.string().nullable()
|
|
3441
|
+
});
|
|
3442
|
+
WalletInvoiceStatus = z.enum(["draft", "open", "paid", "void", "uncollectible"]);
|
|
3443
|
+
WalletInvoice = z.object({
|
|
3444
|
+
id: z.string(),
|
|
3445
|
+
// 'final' = issued when the workspace moved to prepaid.
|
|
3446
|
+
kind: z.enum(["monthly", "final"]),
|
|
3447
|
+
periodStart: z.string(),
|
|
3448
|
+
periodEnd: z.string(),
|
|
3449
|
+
amountUsd: z.string(),
|
|
3450
|
+
status: WalletInvoiceStatus,
|
|
3451
|
+
issuedAt: z.string().nullable(),
|
|
3452
|
+
dueAt: z.string().nullable(),
|
|
3453
|
+
paidAt: z.string().nullable(),
|
|
3454
|
+
// Stripe's hosted page to view and pay it (null until issued).
|
|
3455
|
+
payUrl: z.string().nullable()
|
|
3456
|
+
});
|
|
3457
|
+
z.object({ invoices: z.array(WalletInvoice) });
|
|
3458
|
+
WalletEntryKind = z.enum(["grant", "topup", "usage", "refund", "refund_reversal", "dispute", "dispute_reinstated", "adjustment", "invoice_payment"]);
|
|
3459
|
+
WalletEntry = z.object({
|
|
3460
|
+
id: z.string(),
|
|
3461
|
+
kind: WalletEntryKind,
|
|
3462
|
+
// Signed: a credit is positive, a debit negative.
|
|
3463
|
+
amountUsd: z.string(),
|
|
3464
|
+
balanceAfterUsd: z.string(),
|
|
3465
|
+
// The usage row a debit charged (usage entries only).
|
|
3466
|
+
usageEventId: z.string().nullable(),
|
|
3467
|
+
note: z.string().nullable(),
|
|
3468
|
+
createdAt: z.string()
|
|
3469
|
+
});
|
|
3470
|
+
z.object({
|
|
3471
|
+
entries: z.array(WalletEntry),
|
|
3472
|
+
// Pass as `before` for the next (older) page; null when there is none.
|
|
3473
|
+
nextBefore: z.string().nullable()
|
|
3474
|
+
});
|
|
3475
|
+
TOPUP_MIN_USD = 10;
|
|
3476
|
+
TOPUP_MAX_USD = 500;
|
|
3477
|
+
z.object({ amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD) });
|
|
3478
|
+
z.object({
|
|
3479
|
+
enabled: z.boolean(),
|
|
3480
|
+
thresholdUsd: z.number().min(1).max(TOPUP_MAX_USD),
|
|
3481
|
+
amountUsd: z.number().int().min(TOPUP_MIN_USD).max(TOPUP_MAX_USD)
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
});
|
|
2105
3485
|
|
|
2106
3486
|
// ../contracts/src/index.ts
|
|
2107
3487
|
var init_src = __esm({
|
|
@@ -2114,12 +3494,15 @@ var init_src = __esm({
|
|
|
2114
3494
|
init_agents();
|
|
2115
3495
|
init_process_state();
|
|
2116
3496
|
init_call_events();
|
|
3497
|
+
init_text_turns();
|
|
2117
3498
|
init_call_plan();
|
|
2118
3499
|
init_memory();
|
|
2119
3500
|
init_participants();
|
|
2120
3501
|
init_forms();
|
|
2121
3502
|
init_consultation();
|
|
2122
3503
|
init_agent_config();
|
|
3504
|
+
init_agent_versions();
|
|
3505
|
+
init_providers();
|
|
2123
3506
|
init_model_catalog();
|
|
2124
3507
|
init_evaluations();
|
|
2125
3508
|
init_tests();
|
|
@@ -2134,6 +3517,7 @@ var init_src = __esm({
|
|
|
2134
3517
|
init_flow_validate();
|
|
2135
3518
|
init_flow_builtins();
|
|
2136
3519
|
init_compliance();
|
|
3520
|
+
init_sms_keywords();
|
|
2137
3521
|
init_call_recording();
|
|
2138
3522
|
init_outbound();
|
|
2139
3523
|
init_connection();
|
|
@@ -2148,20 +3532,27 @@ var init_src = __esm({
|
|
|
2148
3532
|
init_playbooks();
|
|
2149
3533
|
init_playbook_versions();
|
|
2150
3534
|
init_playbook_compile();
|
|
3535
|
+
init_effect_stack();
|
|
3536
|
+
init_engine();
|
|
3537
|
+
init_identity();
|
|
3538
|
+
init_interaction();
|
|
3539
|
+
init_channel_connector();
|
|
3540
|
+
init_monitoring();
|
|
3541
|
+
init_wallet();
|
|
2151
3542
|
}
|
|
2152
3543
|
});
|
|
2153
3544
|
|
|
2154
3545
|
// src/runtime/graph/interpolate.ts
|
|
2155
|
-
function interpolate(template,
|
|
3546
|
+
function interpolate(template, scope2) {
|
|
2156
3547
|
if (!template || template.indexOf("{{") === -1) return template;
|
|
2157
3548
|
return template.replace(TOKEN, (_match, expr) => {
|
|
2158
|
-
const value = resolvePath(
|
|
3549
|
+
const value = resolvePath(scope2, expr.trim());
|
|
2159
3550
|
return value === void 0 || value === null ? "" : String(value);
|
|
2160
3551
|
});
|
|
2161
3552
|
}
|
|
2162
|
-
function resolvePath(
|
|
3553
|
+
function resolvePath(scope2, path) {
|
|
2163
3554
|
const parts = path.split(".").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
2164
|
-
let current =
|
|
3555
|
+
let current = scope2;
|
|
2165
3556
|
for (const part of parts) {
|
|
2166
3557
|
if (current === null || typeof current !== "object") return void 0;
|
|
2167
3558
|
const obj = current;
|
|
@@ -2211,6 +3602,20 @@ var init_builtins = __esm({
|
|
|
2211
3602
|
init_src();
|
|
2212
3603
|
}
|
|
2213
3604
|
});
|
|
3605
|
+
function createChatClient(config = {}) {
|
|
3606
|
+
return new OpenAI({
|
|
3607
|
+
...config.apiKey ? { apiKey: config.apiKey } : {},
|
|
3608
|
+
...config.baseURL ? { baseURL: config.baseURL } : {},
|
|
3609
|
+
...config.timeoutMs ? { timeout: config.timeoutMs } : {}
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
var init_dist = __esm({
|
|
3613
|
+
"../llm-client/dist/index.js"() {
|
|
3614
|
+
}
|
|
3615
|
+
});
|
|
3616
|
+
function runWithOpenAIScope(client, fn) {
|
|
3617
|
+
return scope.run(client, fn);
|
|
3618
|
+
}
|
|
2214
3619
|
function defaultOpenAIOptions() {
|
|
2215
3620
|
if (!credential) return {};
|
|
2216
3621
|
return {
|
|
@@ -2218,17 +3623,283 @@ function defaultOpenAIOptions() {
|
|
|
2218
3623
|
...credential.baseURL ? { baseURL: credential.baseURL } : {}
|
|
2219
3624
|
};
|
|
2220
3625
|
}
|
|
3626
|
+
function internalChatClient(opts) {
|
|
3627
|
+
const cred = defaultOpenAIOptions();
|
|
3628
|
+
return createChatClient({
|
|
3629
|
+
...cred.apiKey ? { apiKey: cred.apiKey } : {},
|
|
3630
|
+
...cred.baseURL ? { baseURL: cred.baseURL } : {},
|
|
3631
|
+
...{}
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
2221
3634
|
function defaultOpenAI() {
|
|
2222
|
-
return cached ??=
|
|
3635
|
+
return scope.getStore() ?? (cached ??= internalChatClient());
|
|
2223
3636
|
}
|
|
2224
3637
|
function hasOpenAiKey() {
|
|
2225
|
-
return credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
|
|
3638
|
+
return scope.getStore() !== void 0 || credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
|
|
2226
3639
|
}
|
|
2227
|
-
var credential, cached;
|
|
3640
|
+
var credential, cached, scope;
|
|
2228
3641
|
var init_openai_default = __esm({
|
|
2229
3642
|
"src/runtime/openai-default.ts"() {
|
|
3643
|
+
init_dist();
|
|
2230
3644
|
credential = null;
|
|
2231
3645
|
cached = null;
|
|
3646
|
+
scope = new AsyncLocalStorage();
|
|
3647
|
+
}
|
|
3648
|
+
});
|
|
3649
|
+
var init_ssrf = __esm({
|
|
3650
|
+
"src/brain/ssrf.ts"() {
|
|
3651
|
+
}
|
|
3652
|
+
});
|
|
3653
|
+
|
|
3654
|
+
// src/brain/serialize.ts
|
|
3655
|
+
var init_serialize = __esm({
|
|
3656
|
+
"src/brain/serialize.ts"() {
|
|
3657
|
+
}
|
|
3658
|
+
});
|
|
3659
|
+
|
|
3660
|
+
// src/brain/transport-callback.ts
|
|
3661
|
+
var init_transport_callback = __esm({
|
|
3662
|
+
"src/brain/transport-callback.ts"() {
|
|
3663
|
+
init_serialize();
|
|
3664
|
+
}
|
|
3665
|
+
});
|
|
3666
|
+
|
|
3667
|
+
// src/brain/connector-llm.ts
|
|
3668
|
+
var init_connector_llm = __esm({
|
|
3669
|
+
"src/brain/connector-llm.ts"() {
|
|
3670
|
+
init_serialize();
|
|
3671
|
+
}
|
|
3672
|
+
});
|
|
3673
|
+
var init_wrap = __esm({
|
|
3674
|
+
"src/providers/wrap.ts"() {
|
|
3675
|
+
}
|
|
3676
|
+
});
|
|
3677
|
+
|
|
3678
|
+
// src/providers/index.ts
|
|
3679
|
+
async function importOptional(spec, hint) {
|
|
3680
|
+
try {
|
|
3681
|
+
return await import(spec);
|
|
3682
|
+
} catch {
|
|
3683
|
+
throw new Error(`${hint}: "${spec}" is not installed. Add it with: pnpm add ${spec}`);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
function withCreds(opts, creds2) {
|
|
3687
|
+
if (creds2?.apiKey) opts["apiKey"] = creds2.apiKey;
|
|
3688
|
+
if (creds2?.baseURL) opts["baseURL"] = creds2.baseURL;
|
|
3689
|
+
return opts;
|
|
3690
|
+
}
|
|
3691
|
+
var deepgram, openai, anthropic, cartesia, elevenlabs, assemblyai, google, silero;
|
|
3692
|
+
var init_providers2 = __esm({
|
|
3693
|
+
"src/providers/index.ts"() {
|
|
3694
|
+
init_ssrf();
|
|
3695
|
+
init_transport_callback();
|
|
3696
|
+
init_connector_llm();
|
|
3697
|
+
init_wrap();
|
|
3698
|
+
deepgram = {
|
|
3699
|
+
stt(options = {}) {
|
|
3700
|
+
return async () => {
|
|
3701
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.stt");
|
|
3702
|
+
return new dg.STT(
|
|
3703
|
+
withCreds(
|
|
3704
|
+
{ model: options.model ?? "nova-3", language: options.language ?? "en-US" },
|
|
3705
|
+
options
|
|
3706
|
+
)
|
|
3707
|
+
);
|
|
3708
|
+
};
|
|
3709
|
+
},
|
|
3710
|
+
tts(options = {}) {
|
|
3711
|
+
return async () => {
|
|
3712
|
+
const dg = await importOptional("@voicelayer/agents-plugin-deepgram", "deepgram.tts");
|
|
3713
|
+
return new dg.TTS(
|
|
3714
|
+
withCreds({ model: options.model ?? "aura-2-harmonia-en" }, options)
|
|
3715
|
+
);
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3719
|
+
openai = {
|
|
3720
|
+
llm(options = {}) {
|
|
3721
|
+
return async () => {
|
|
3722
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.llm");
|
|
3723
|
+
return new oa.LLM(
|
|
3724
|
+
withCreds({ model: options.model ?? "gpt-4o-mini" }, options)
|
|
3725
|
+
);
|
|
3726
|
+
};
|
|
3727
|
+
},
|
|
3728
|
+
tts(options = {}) {
|
|
3729
|
+
return async () => {
|
|
3730
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.tts");
|
|
3731
|
+
const opts = { model: options.model ?? "tts-1" };
|
|
3732
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3733
|
+
if (options.instructions) opts["instructions"] = options.instructions;
|
|
3734
|
+
return new oa.TTS(withCreds(opts, options));
|
|
3735
|
+
};
|
|
3736
|
+
},
|
|
3737
|
+
// Speech-to-speech via the OpenAI Realtime API. Returns a RealtimeProvider
|
|
3738
|
+
// that the SDK uses in place of the stt/llm/tts pipeline.
|
|
3739
|
+
realtime(options = {}) {
|
|
3740
|
+
return async () => {
|
|
3741
|
+
const oa = await importOptional("@voicelayer/agents-plugin-openai", "openai.realtime");
|
|
3742
|
+
const opts = {};
|
|
3743
|
+
if (options.model) opts["model"] = options.model;
|
|
3744
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3745
|
+
return new oa.realtime.RealtimeModel(withCreds(opts, options));
|
|
3746
|
+
};
|
|
3747
|
+
}
|
|
3748
|
+
};
|
|
3749
|
+
anthropic = {
|
|
3750
|
+
llm(options = {}) {
|
|
3751
|
+
return async () => {
|
|
3752
|
+
const an = await importOptional("@livekit/agents-plugin-anthropic", "anthropic.llm");
|
|
3753
|
+
return new an.LLM(
|
|
3754
|
+
withCreds({ model: options.model ?? "claude-sonnet-4" }, options)
|
|
3755
|
+
);
|
|
3756
|
+
};
|
|
3757
|
+
}
|
|
3758
|
+
};
|
|
3759
|
+
cartesia = {
|
|
3760
|
+
tts(options = {}) {
|
|
3761
|
+
return async () => {
|
|
3762
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.tts");
|
|
3763
|
+
return new ct.TTS(options);
|
|
3764
|
+
};
|
|
3765
|
+
},
|
|
3766
|
+
stt(options = {}) {
|
|
3767
|
+
return async () => {
|
|
3768
|
+
const ct = await importOptional("@voicelayer/agents-plugin-cartesia", "cartesia.stt");
|
|
3769
|
+
const opts = {};
|
|
3770
|
+
if (options.model) opts["model"] = options.model;
|
|
3771
|
+
if (options.language) opts["language"] = options.language;
|
|
3772
|
+
return new ct.STT(withCreds(opts, options));
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
};
|
|
3776
|
+
elevenlabs = {
|
|
3777
|
+
tts(options = {}) {
|
|
3778
|
+
return async () => {
|
|
3779
|
+
const el = await importOptional("@voicelayer/agents-plugin-elevenlabs", "elevenlabs.tts");
|
|
3780
|
+
const opts = {};
|
|
3781
|
+
if (options.voice) opts["voiceId"] = options.voice;
|
|
3782
|
+
if (options.model) opts["model"] = options.model;
|
|
3783
|
+
return new el.TTS(withCreds(opts, options));
|
|
3784
|
+
};
|
|
3785
|
+
}
|
|
3786
|
+
};
|
|
3787
|
+
assemblyai = {
|
|
3788
|
+
stt(options = {}) {
|
|
3789
|
+
return async () => {
|
|
3790
|
+
const aai = await importOptional("@voicelayer/agents-plugin-assemblyai", "assemblyai.stt");
|
|
3791
|
+
const opts = {};
|
|
3792
|
+
if (options.language) opts["language"] = options.language;
|
|
3793
|
+
return new aai.STT(withCreds(opts, options));
|
|
3794
|
+
};
|
|
3795
|
+
}
|
|
3796
|
+
};
|
|
3797
|
+
google = {
|
|
3798
|
+
llm(options = {}) {
|
|
3799
|
+
return async () => {
|
|
3800
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.llm");
|
|
3801
|
+
return new g.LLM(
|
|
3802
|
+
withCreds({ model: options.model ?? "gemini-2.0-flash" }, options)
|
|
3803
|
+
);
|
|
3804
|
+
};
|
|
3805
|
+
},
|
|
3806
|
+
realtime(options = {}) {
|
|
3807
|
+
return async () => {
|
|
3808
|
+
const g = await importOptional("@voicelayer/agents-plugin-google", "google.realtime");
|
|
3809
|
+
const opts = {};
|
|
3810
|
+
if (options.model) opts["model"] = options.model;
|
|
3811
|
+
if (options.voice) opts["voice"] = options.voice;
|
|
3812
|
+
return new g.beta.realtime.RealtimeModel(withCreds(opts, options));
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
};
|
|
3816
|
+
silero = {
|
|
3817
|
+
vad() {
|
|
3818
|
+
return async () => {
|
|
3819
|
+
const sil = await importOptional("@voicelayer/agents-plugin-silero", "silero.vad");
|
|
3820
|
+
return await sil.VAD.load();
|
|
3821
|
+
};
|
|
3822
|
+
}
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
});
|
|
3826
|
+
|
|
3827
|
+
// src/providers/registry.ts
|
|
3828
|
+
function entry(id, bits) {
|
|
3829
|
+
const identity = providerIdentity(id);
|
|
3830
|
+
if (!identity) throw new Error(`PROVIDER_REGISTRY: no provider identity in contracts for '${id}'`);
|
|
3831
|
+
return { name: identity.id, capabilities: identity.capabilities, ...bits };
|
|
3832
|
+
}
|
|
3833
|
+
var model, lang, voice, creds;
|
|
3834
|
+
var init_registry = __esm({
|
|
3835
|
+
"src/providers/registry.ts"() {
|
|
3836
|
+
init_providers2();
|
|
3837
|
+
init_src();
|
|
3838
|
+
model = (o) => o.model ? { model: o.model } : {};
|
|
3839
|
+
lang = (o) => o.language ? { language: o.language } : {};
|
|
3840
|
+
voice = (o) => o.voice ? { voice: o.voice } : {};
|
|
3841
|
+
creds = (o) => o.creds ?? {};
|
|
3842
|
+
({
|
|
3843
|
+
deepgram: entry("deepgram", {
|
|
3844
|
+
failureClass: "vendor-api",
|
|
3845
|
+
engines: ["livekit"],
|
|
3846
|
+
stt: (o) => deepgram.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
3847
|
+
// Deepgram folds the voice into the model id (aura-2-<name>-en): wire config
|
|
3848
|
+
// passes a voice, code config passes a model — both land as `model`.
|
|
3849
|
+
tts: (o) => {
|
|
3850
|
+
const m = o.voice ?? o.model;
|
|
3851
|
+
return deepgram.tts({ ...m ? { model: m } : {}, ...creds(o) });
|
|
3852
|
+
}
|
|
3853
|
+
}),
|
|
3854
|
+
openai: entry("openai", {
|
|
3855
|
+
failureClass: "vendor-api",
|
|
3856
|
+
engines: ["livekit", "text"],
|
|
3857
|
+
llm: (o) => openai.llm({ ...model(o), ...creds(o) }),
|
|
3858
|
+
tts: (o) => openai.tts({ ...voice(o), ...model(o), ...creds(o) }),
|
|
3859
|
+
realtime: (o) => openai.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
3860
|
+
}),
|
|
3861
|
+
anthropic: entry("anthropic", {
|
|
3862
|
+
failureClass: "vendor-api",
|
|
3863
|
+
engines: ["livekit", "text"],
|
|
3864
|
+
llm: (o) => anthropic.llm({ ...model(o), ...creds(o) })
|
|
3865
|
+
}),
|
|
3866
|
+
google: entry("google", {
|
|
3867
|
+
failureClass: "vendor-api",
|
|
3868
|
+
engines: ["livekit", "text"],
|
|
3869
|
+
llm: (o) => google.llm({ ...model(o), ...creds(o) }),
|
|
3870
|
+
realtime: (o) => google.realtime({ ...model(o), ...voice(o), ...creds(o) })
|
|
3871
|
+
}),
|
|
3872
|
+
cartesia: entry("cartesia", {
|
|
3873
|
+
failureClass: "vendor-api",
|
|
3874
|
+
engines: ["livekit"],
|
|
3875
|
+
stt: (o) => cartesia.stt({ ...model(o), ...lang(o), ...creds(o) }),
|
|
3876
|
+
tts: (o) => cartesia.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
3877
|
+
}),
|
|
3878
|
+
elevenlabs: entry("elevenlabs", {
|
|
3879
|
+
failureClass: "vendor-api",
|
|
3880
|
+
engines: ["livekit"],
|
|
3881
|
+
tts: (o) => elevenlabs.tts({ ...voice(o), ...model(o), ...creds(o) })
|
|
3882
|
+
}),
|
|
3883
|
+
assemblyai: entry("assemblyai", {
|
|
3884
|
+
failureClass: "vendor-api",
|
|
3885
|
+
engines: ["livekit"],
|
|
3886
|
+
stt: (o) => assemblyai.stt({ ...lang(o), ...creds(o) })
|
|
3887
|
+
// no model knob
|
|
3888
|
+
}),
|
|
3889
|
+
silero: entry("silero", {
|
|
3890
|
+
failureClass: "local",
|
|
3891
|
+
engines: ["livekit"],
|
|
3892
|
+
vad: () => silero.vad()
|
|
3893
|
+
})
|
|
3894
|
+
});
|
|
3895
|
+
}
|
|
3896
|
+
});
|
|
3897
|
+
|
|
3898
|
+
// src/providers/llm.ts
|
|
3899
|
+
var init_llm = __esm({
|
|
3900
|
+
"src/providers/llm.ts"() {
|
|
3901
|
+
init_openai_default();
|
|
3902
|
+
init_registry();
|
|
2232
3903
|
}
|
|
2233
3904
|
});
|
|
2234
3905
|
|
|
@@ -2241,6 +3912,7 @@ function dotProduct(a, b) {
|
|
|
2241
3912
|
}
|
|
2242
3913
|
function createOpenAIEmbedder() {
|
|
2243
3914
|
const cache = /* @__PURE__ */ new Map();
|
|
3915
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
2244
3916
|
const available = () => hasOpenAiKey();
|
|
2245
3917
|
function remember(key, vec) {
|
|
2246
3918
|
if (cache.size >= CACHE_MAX) {
|
|
@@ -2259,24 +3931,31 @@ function createOpenAIEmbedder() {
|
|
|
2259
3931
|
if (!available() || !key) return null;
|
|
2260
3932
|
const hit = cache.get(key);
|
|
2261
3933
|
if (hit) return hit;
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
const
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
})
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
3934
|
+
const pending = inflight.get(key);
|
|
3935
|
+
if (pending) return pending;
|
|
3936
|
+
const request = (async () => {
|
|
3937
|
+
const controller = new AbortController();
|
|
3938
|
+
const timer = setTimeout(() => controller.abort(), EMBED_TIMEOUT_MS);
|
|
3939
|
+
try {
|
|
3940
|
+
const res = await defaultOpenAI().embeddings.create(
|
|
3941
|
+
{ model: EMBED_MODEL, input: key },
|
|
3942
|
+
{ signal: controller.signal }
|
|
3943
|
+
);
|
|
3944
|
+
const vec = res.data[0]?.embedding;
|
|
3945
|
+
if (!Array.isArray(vec)) return null;
|
|
3946
|
+
return remember(key, vec);
|
|
3947
|
+
} catch (err) {
|
|
3948
|
+
graphWarn("embed failed; router will fall back to judge", {
|
|
3949
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3950
|
+
});
|
|
3951
|
+
return null;
|
|
3952
|
+
} finally {
|
|
3953
|
+
clearTimeout(timer);
|
|
3954
|
+
inflight.delete(key);
|
|
3955
|
+
}
|
|
3956
|
+
})();
|
|
3957
|
+
inflight.set(key, request);
|
|
3958
|
+
return request;
|
|
2280
3959
|
},
|
|
2281
3960
|
prewarm(text) {
|
|
2282
3961
|
void this.embed(text).catch(() => {
|
|
@@ -2292,7 +3971,8 @@ function getEmbedder() {
|
|
|
2292
3971
|
var EMBED_MODEL, ROUTE_THRESHOLD, EMBED_TIMEOUT_MS, CACHE_MAX, singleton, override;
|
|
2293
3972
|
var init_embedder = __esm({
|
|
2294
3973
|
"src/runtime/graph/embedder.ts"() {
|
|
2295
|
-
|
|
3974
|
+
init_log();
|
|
3975
|
+
init_llm();
|
|
2296
3976
|
EMBED_MODEL = process.env["VOICELAYER_EMBED_MODEL"] ?? "text-embedding-3-small";
|
|
2297
3977
|
ROUTE_THRESHOLD = Number(process.env["VL_FLOW_ROUTE_THRESHOLD"] ?? "0.62");
|
|
2298
3978
|
EMBED_TIMEOUT_MS = Number(process.env["VL_FLOW_EMBED_TIMEOUT_MS"] ?? "2500");
|
|
@@ -2354,7 +4034,7 @@ async function routeByEmbedding(nl, ctx) {
|
|
|
2354
4034
|
if (!best || score > best.score) best = { to: edge.to, score };
|
|
2355
4035
|
}
|
|
2356
4036
|
if (best && best.score >= ROUTE_THRESHOLD) {
|
|
2357
|
-
|
|
4037
|
+
graphLog("route: embedding", { to: best.to, score: Number(best.score.toFixed(3)) });
|
|
2358
4038
|
return best.to;
|
|
2359
4039
|
}
|
|
2360
4040
|
return null;
|
|
@@ -2380,11 +4060,21 @@ async function evaluateCondition(condition, ctx) {
|
|
|
2380
4060
|
function splitTop(text, op) {
|
|
2381
4061
|
return text.split(op);
|
|
2382
4062
|
}
|
|
4063
|
+
function conditionReadsSlots(condition) {
|
|
4064
|
+
const text = condition.trim();
|
|
4065
|
+
if (!text) return false;
|
|
4066
|
+
return splitTop(text, "||").flatMap((part) => splitTop(part, "&&")).map((atom) => atom.trim()).some((atom) => {
|
|
4067
|
+
const lower = atom.toLowerCase();
|
|
4068
|
+
if (lower === "true" || lower === "always" || lower === "false" || lower === "never") return false;
|
|
4069
|
+
if (TOOL_ATOM.test(atom) || NODE_ATOM.test(atom) || DTMF_ATOM.test(atom)) return false;
|
|
4070
|
+
return OP_ATOM.test(atom) || SLOT_PRESENCE_ATOM.test(atom) || BARE_ATOM.test(atom);
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
2383
4073
|
function evaluateDeterministic(atom, ctx) {
|
|
2384
4074
|
const lower = atom.toLowerCase();
|
|
2385
4075
|
if (lower === "true" || lower === "always") return true;
|
|
2386
4076
|
if (lower === "false" || lower === "never") return false;
|
|
2387
|
-
const toolMatch = atom.match(
|
|
4077
|
+
const toolMatch = atom.match(TOOL_ATOM);
|
|
2388
4078
|
if (toolMatch) {
|
|
2389
4079
|
const [, id, path, op, rawVal] = toolMatch;
|
|
2390
4080
|
const result = ctx.tools[id];
|
|
@@ -2398,7 +4088,7 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2398
4088
|
const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
|
|
2399
4089
|
return op === "!=" ? !eq : eq;
|
|
2400
4090
|
}
|
|
2401
|
-
const nodeMatch = atom.match(
|
|
4091
|
+
const nodeMatch = atom.match(NODE_ATOM);
|
|
2402
4092
|
if (nodeMatch) {
|
|
2403
4093
|
if (!ctx.nodeResults) return void 0;
|
|
2404
4094
|
const [, id, path, op, rawVal] = nodeMatch;
|
|
@@ -2410,14 +4100,12 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2410
4100
|
const eq = String(actual ?? "").toLowerCase() === (rawVal ?? "").trim().toLowerCase();
|
|
2411
4101
|
return op === "!=" ? !eq : eq;
|
|
2412
4102
|
}
|
|
2413
|
-
const dtmfMatch = atom.match(
|
|
4103
|
+
const dtmfMatch = atom.match(DTMF_ATOM);
|
|
2414
4104
|
if (dtmfMatch) {
|
|
2415
4105
|
if (ctx.lastDtmf === void 0) return void 0;
|
|
2416
4106
|
return ctx.lastDtmf === dtmfMatch[1].trim();
|
|
2417
4107
|
}
|
|
2418
|
-
const opMatch = atom.match(
|
|
2419
|
-
/^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i
|
|
2420
|
-
);
|
|
4108
|
+
const opMatch = atom.match(OP_ATOM);
|
|
2421
4109
|
if (opMatch) {
|
|
2422
4110
|
const [, neg, nameRaw, op, valRaw] = opMatch;
|
|
2423
4111
|
const raw = resolvePath(ctx.slots, nameRaw.trim());
|
|
@@ -2439,14 +4127,14 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2439
4127
|
else res = value === target;
|
|
2440
4128
|
return neg ? !res : res;
|
|
2441
4129
|
}
|
|
2442
|
-
const slotPres = atom.match(
|
|
4130
|
+
const slotPres = atom.match(SLOT_PRESENCE_ATOM);
|
|
2443
4131
|
if (slotPres) {
|
|
2444
4132
|
const [, neg, nameRaw, kw] = slotPres;
|
|
2445
4133
|
const present = hasSlot(ctx.slots, nameRaw.trim());
|
|
2446
4134
|
const res = (kw ?? "").toLowerCase() === "empty" ? !present : present;
|
|
2447
4135
|
return neg ? !res : res;
|
|
2448
4136
|
}
|
|
2449
|
-
const bare = atom.match(
|
|
4137
|
+
const bare = atom.match(BARE_ATOM);
|
|
2450
4138
|
if (bare) {
|
|
2451
4139
|
const [, neg, name, kw] = bare;
|
|
2452
4140
|
const present = hasSlot(ctx.slots, name);
|
|
@@ -2457,13 +4145,27 @@ function evaluateDeterministic(atom, ctx) {
|
|
|
2457
4145
|
}
|
|
2458
4146
|
async function pickOption(options, ctx, hint) {
|
|
2459
4147
|
if (options.length === 0) return null;
|
|
2460
|
-
|
|
4148
|
+
return judgeSettled(ctx, (collected) => pickWith(options, collected, ctx.lastUtterance, hint));
|
|
4149
|
+
}
|
|
4150
|
+
function collectedOf(slots) {
|
|
4151
|
+
return Object.entries(slots).filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => `${k.trim()}: ${String(v)}`).join("; ");
|
|
4152
|
+
}
|
|
4153
|
+
async function judgeSettled(ctx, run) {
|
|
4154
|
+
const before = collectedOf(ctx.slots);
|
|
4155
|
+
if (!ctx.settle) return run(before);
|
|
4156
|
+
const [speculative, fresh] = await Promise.all([run(before), ctx.settle()]);
|
|
4157
|
+
const after = collectedOf(fresh);
|
|
4158
|
+
if (after === before) return speculative;
|
|
4159
|
+
graphLog("judge: capture changed context; re-judging");
|
|
4160
|
+
return run(after);
|
|
4161
|
+
}
|
|
4162
|
+
async function pickWith(options, collected, lastUtterance, hint) {
|
|
2461
4163
|
const list = options.map((o, i) => `${i + 1}. ${o.text}`).join("\n");
|
|
2462
4164
|
const prompt = [
|
|
2463
4165
|
"Pick which outcome best matches the current state of this voice conversation.",
|
|
2464
4166
|
...hint && hint.trim() ? [`What decides the path: ${hint.trim()}`] : [],
|
|
2465
4167
|
`Information collected: ${collected || "(none)"}`,
|
|
2466
|
-
`The caller most recently said: "${
|
|
4168
|
+
`The caller most recently said: "${lastUtterance ?? ""}"`,
|
|
2467
4169
|
"Outcomes:",
|
|
2468
4170
|
list,
|
|
2469
4171
|
'Answer with ONLY the outcome number, or "none" if nothing clearly matches.'
|
|
@@ -2488,7 +4190,7 @@ async function pickOption(options, ctx, hint) {
|
|
|
2488
4190
|
if (!Number.isInteger(n) || n < 1 || n > options.length) return null;
|
|
2489
4191
|
return options[n - 1]?.id ?? null;
|
|
2490
4192
|
} catch (err) {
|
|
2491
|
-
|
|
4193
|
+
graphWarn("option pick failed; treating as undecided", {
|
|
2492
4194
|
err: err instanceof Error ? err.message : String(err)
|
|
2493
4195
|
});
|
|
2494
4196
|
return null;
|
|
@@ -2497,12 +4199,18 @@ async function pickOption(options, ctx, hint) {
|
|
|
2497
4199
|
}
|
|
2498
4200
|
}
|
|
2499
4201
|
async function judge(condition, ctx) {
|
|
2500
|
-
|
|
4202
|
+
return judgeSettled(ctx, (collected) => judgeWith(condition, collected, ctx.lastUtterance));
|
|
4203
|
+
}
|
|
4204
|
+
async function judgeWith(condition, collected, lastUtterance) {
|
|
2501
4205
|
const prompt = [
|
|
2502
4206
|
"You judge whether a condition about an ongoing voice conversation is currently TRUE.",
|
|
2503
4207
|
`Information collected: ${collected || "(none)"}`,
|
|
2504
|
-
`The caller most recently said: "${
|
|
4208
|
+
`The caller most recently said: "${lastUtterance ?? ""}"`,
|
|
2505
4209
|
`Condition: ${condition}`,
|
|
4210
|
+
// Observed live: "Keystone Arena." (a site name) was judged to satisfy
|
|
4211
|
+
// "caller described a network problem". The judge must not fill in what
|
|
4212
|
+
// the caller probably meant — only what they actually said counts.
|
|
4213
|
+
'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
4214
|
'Answer strictly "yes" or "no".'
|
|
2507
4215
|
].join("\n");
|
|
2508
4216
|
const controller = new AbortController();
|
|
@@ -2522,7 +4230,7 @@ async function judge(condition, ctx) {
|
|
|
2522
4230
|
);
|
|
2523
4231
|
return (res.choices[0]?.message?.content ?? "").trim().toLowerCase().startsWith("y");
|
|
2524
4232
|
} catch (err) {
|
|
2525
|
-
|
|
4233
|
+
graphWarn("condition judge failed; treating as false", {
|
|
2526
4234
|
condition,
|
|
2527
4235
|
err: err instanceof Error ? err.message : String(err)
|
|
2528
4236
|
});
|
|
@@ -2531,13 +4239,20 @@ async function judge(condition, ctx) {
|
|
|
2531
4239
|
clearTimeout(timer);
|
|
2532
4240
|
}
|
|
2533
4241
|
}
|
|
2534
|
-
var JUDGE_TIMEOUT_MS;
|
|
4242
|
+
var JUDGE_TIMEOUT_MS, TOOL_ATOM, NODE_ATOM, DTMF_ATOM, OP_ATOM, SLOT_PRESENCE_ATOM, BARE_ATOM;
|
|
2535
4243
|
var init_conditions = __esm({
|
|
2536
4244
|
"src/runtime/graph/conditions.ts"() {
|
|
2537
|
-
|
|
4245
|
+
init_log();
|
|
4246
|
+
init_llm();
|
|
2538
4247
|
init_interpolate();
|
|
2539
4248
|
init_embedder();
|
|
2540
4249
|
JUDGE_TIMEOUT_MS = 4e3;
|
|
4250
|
+
TOOL_ATOM = /^tool[.:]([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
|
|
4251
|
+
NODE_ATOM = /^node:([A-Za-z0-9_-]+)(?:\.([\w.]+))?\s*(==|!=)?\s*(.*)$/;
|
|
4252
|
+
DTMF_ATOM = /^dtmf:\s*(.+)$/i;
|
|
4253
|
+
OP_ATOM = /^(!?)\s*(?:slot:)?(.+?)\s*(==|!=|=|contains|starts_with|greater_than|less_than|>=|<=|>|<)\s*(.+)$/i;
|
|
4254
|
+
SLOT_PRESENCE_ATOM = /^(!?)\s*slot:(.+?)\s*(present|empty)?$/i;
|
|
4255
|
+
BARE_ATOM = /^(!?)\s*([\w.-]+)\s*(present|empty)?$/i;
|
|
2541
4256
|
}
|
|
2542
4257
|
});
|
|
2543
4258
|
|
|
@@ -2580,7 +4295,7 @@ async function resolveBranch(branches, ctx, opts) {
|
|
|
2580
4295
|
if (!best || score > best.score) best = { id: b.id, score };
|
|
2581
4296
|
}
|
|
2582
4297
|
if (best && best.score >= ROUTE_THRESHOLD) {
|
|
2583
|
-
|
|
4298
|
+
graphLog("branch: embedding", { id: best.id, score: Number(best.score.toFixed(3)) });
|
|
2584
4299
|
return best.id;
|
|
2585
4300
|
}
|
|
2586
4301
|
}
|
|
@@ -2595,6 +4310,7 @@ async function resolveBranch(branches, ctx, opts) {
|
|
|
2595
4310
|
var ELSE_BRANCH_ID;
|
|
2596
4311
|
var init_branch_resolve = __esm({
|
|
2597
4312
|
"src/runtime/graph/branch-resolve.ts"() {
|
|
4313
|
+
init_log();
|
|
2598
4314
|
init_conditions();
|
|
2599
4315
|
init_embedder();
|
|
2600
4316
|
ELSE_BRANCH_ID = "else";
|
|
@@ -2694,13 +4410,18 @@ function scopeFor(state, deps) {
|
|
|
2694
4410
|
function str(value) {
|
|
2695
4411
|
return typeof value === "string" ? value : "";
|
|
2696
4412
|
}
|
|
4413
|
+
async function settledSlots(deps) {
|
|
4414
|
+
await deps.utterances.settle?.();
|
|
4415
|
+
return deps.processRt.getData();
|
|
4416
|
+
}
|
|
2697
4417
|
function evalCtxOf(state, deps) {
|
|
2698
4418
|
return {
|
|
2699
4419
|
slots: deps.processRt.getData(),
|
|
2700
4420
|
tools: state.toolResults,
|
|
2701
4421
|
nodeResults: state.nodeResults,
|
|
2702
4422
|
...state.lastUtterance !== void 0 ? { lastUtterance: state.lastUtterance } : {},
|
|
2703
|
-
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {}
|
|
4423
|
+
...state.lastDtmf !== void 0 ? { lastDtmf: state.lastDtmf } : {},
|
|
4424
|
+
settle: () => settledSlots(deps)
|
|
2704
4425
|
};
|
|
2705
4426
|
}
|
|
2706
4427
|
function deterministicEdgeTarget(edges, ctx) {
|
|
@@ -2715,7 +4436,7 @@ async function speak(text, deps, opts) {
|
|
|
2715
4436
|
const allowInterruptions = opts?.allowInterruptions === true;
|
|
2716
4437
|
if (deps.ctx.adapter) {
|
|
2717
4438
|
const r = await deps.ctx.adapter.say(text, { allowInterruptions });
|
|
2718
|
-
|
|
4439
|
+
graphLog("said", { chars: text.length, interrupted: r.interrupted });
|
|
2719
4440
|
return;
|
|
2720
4441
|
}
|
|
2721
4442
|
const session = deps.ctx.livekit.session;
|
|
@@ -2725,12 +4446,12 @@ async function speak(text, deps, opts) {
|
|
|
2725
4446
|
|
|
2726
4447
|
${text}`
|
|
2727
4448
|
}).waitForPlayout();
|
|
2728
|
-
|
|
4449
|
+
graphLog("said (realtime)", { chars: text.length });
|
|
2729
4450
|
return;
|
|
2730
4451
|
}
|
|
2731
4452
|
const handle = session.say(text, { allowInterruptions });
|
|
2732
4453
|
await handle.waitForPlayout();
|
|
2733
|
-
|
|
4454
|
+
graphLog("said", { chars: text.length, interrupted: handle.interrupted });
|
|
2734
4455
|
}
|
|
2735
4456
|
async function genReply(deps, instructions) {
|
|
2736
4457
|
if (deps.ctx.adapter) {
|
|
@@ -2739,10 +4460,13 @@ async function genReply(deps, instructions) {
|
|
|
2739
4460
|
}
|
|
2740
4461
|
await deps.ctx.livekit.session.generateReply({ instructions }).waitForPlayout();
|
|
2741
4462
|
}
|
|
2742
|
-
async function nextTurn(deps, reprompt) {
|
|
4463
|
+
async function nextTurn(deps, reprompt, opts) {
|
|
2743
4464
|
let reprompted = false;
|
|
2744
4465
|
for (; ; ) {
|
|
2745
|
-
const evt = await deps.utterances.next({
|
|
4466
|
+
const evt = await deps.utterances.next({
|
|
4467
|
+
timeoutMs: NO_INPUT_MS,
|
|
4468
|
+
...opts?.deferCapture ? { deferCapture: true } : {}
|
|
4469
|
+
});
|
|
2746
4470
|
if (evt.kind === "hangup") return { result: { kind: "end" } };
|
|
2747
4471
|
if (evt.kind === "control") return { result: evt.result };
|
|
2748
4472
|
if (evt.kind === "timeout") {
|
|
@@ -2816,6 +4540,7 @@ function menuLabelOf(label) {
|
|
|
2816
4540
|
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
4541
|
var init_executors = __esm({
|
|
2818
4542
|
"src/runtime/graph/executors.ts"() {
|
|
4543
|
+
init_log();
|
|
2819
4544
|
init_src();
|
|
2820
4545
|
init_interpolate();
|
|
2821
4546
|
init_builtins();
|
|
@@ -2948,6 +4673,7 @@ var init_executors = __esm({
|
|
|
2948
4673
|
const policy = historyPolicyOf(node);
|
|
2949
4674
|
const maxTurns = hub ? HUB_MAX_TURNS : LLM_MAX_TURNS;
|
|
2950
4675
|
const base = interpolate(str(node.config.instructions), scopeFor(state, deps)) || "Help the caller using everything collected so far. Keep replies short and natural.";
|
|
4676
|
+
const deferCapture = !conditional.some((e) => conditionReadsSlots(e.condition));
|
|
2951
4677
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
2952
4678
|
const collected = formatCollected(deps.processRt.getData());
|
|
2953
4679
|
const parts = [base];
|
|
@@ -2960,25 +4686,19 @@ ${collected}`);
|
|
|
2960
4686
|
() => guardedGenerate(node, state, deps, parts.join("\n\n"), base)
|
|
2961
4687
|
);
|
|
2962
4688
|
if (!hub && !terminal && conditional.length === 0) return { kind: "advance" };
|
|
2963
|
-
const t = await nextTurn(deps);
|
|
4689
|
+
const t = await nextTurn(deps, void 0, { deferCapture });
|
|
2964
4690
|
if ("result" in t) return t.result;
|
|
2965
4691
|
state.lastUtterance = t.text;
|
|
2966
4692
|
state.lastDtmf = t.dtmf;
|
|
2967
4693
|
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
|
-
});
|
|
4694
|
+
const target = await selectEdge(out, evalCtxOf(state, deps));
|
|
2975
4695
|
if (target) {
|
|
2976
|
-
|
|
4696
|
+
graphLog("llm: transition", { node: node.id, to: target, hub });
|
|
2977
4697
|
return { kind: "goto", to: target };
|
|
2978
4698
|
}
|
|
2979
4699
|
}
|
|
2980
4700
|
}
|
|
2981
|
-
|
|
4701
|
+
graphLog("llm: turn cap reached", { node: node.id, hub });
|
|
2982
4702
|
return terminal ? { kind: "end" } : { kind: "advance" };
|
|
2983
4703
|
};
|
|
2984
4704
|
toolExecutor = async (node, state, deps) => {
|
|
@@ -3337,7 +5057,7 @@ ${collected}`);
|
|
|
3337
5057
|
const goal = str(cfg.goal);
|
|
3338
5058
|
let instr = str(cfg.instructions);
|
|
3339
5059
|
const toolNames = Array.isArray(cfg.tools) ? cfg.tools.filter((x) => typeof x === "string" && x.length > 0) : [];
|
|
3340
|
-
let
|
|
5060
|
+
let model2 = str(cfg.model) || void 0;
|
|
3341
5061
|
let temperature = typeof cfg.temperature === "number" ? cfg.temperature : void 0;
|
|
3342
5062
|
let libraryExits = [];
|
|
3343
5063
|
const ref = str(cfg.playbookRef);
|
|
@@ -3347,7 +5067,7 @@ ${collected}`);
|
|
|
3347
5067
|
if (!instr) instr = resolved.instructions;
|
|
3348
5068
|
for (const f of resolved.requiredFields) if (!required.includes(f)) required.push(f);
|
|
3349
5069
|
for (const t of resolved.tools ?? []) if (!toolNames.includes(t)) toolNames.push(t);
|
|
3350
|
-
if (!
|
|
5070
|
+
if (!model2 && resolved.model) model2 = resolved.model;
|
|
3351
5071
|
if (temperature === void 0 && resolved.temperature !== void 0) temperature = resolved.temperature;
|
|
3352
5072
|
libraryExits = resolved.exits ?? [];
|
|
3353
5073
|
}
|
|
@@ -3401,7 +5121,7 @@ ${collected}`);
|
|
|
3401
5121
|
exits: exitOptions,
|
|
3402
5122
|
captureVars: required,
|
|
3403
5123
|
runTool: (name, args) => registry ? registry.run(name, args) : Promise.resolve({ ok: false, status: 0, data: { error: "no tool registry" } }),
|
|
3404
|
-
...
|
|
5124
|
+
...model2 ? { model: model2 } : {},
|
|
3405
5125
|
...temperature !== void 0 ? { temperature } : {}
|
|
3406
5126
|
});
|
|
3407
5127
|
} finally {
|
|
@@ -3594,18 +5314,18 @@ var interpreter_exports = {};
|
|
|
3594
5314
|
__export(interpreter_exports, {
|
|
3595
5315
|
runFlowGraph: () => runFlowGraph
|
|
3596
5316
|
});
|
|
3597
|
-
function pickEntry(
|
|
3598
|
-
return
|
|
5317
|
+
function pickEntry(model2) {
|
|
5318
|
+
return model2.entry.length > 0 ? model2.entry[0] : null;
|
|
3599
5319
|
}
|
|
3600
|
-
async function selectNext(
|
|
5320
|
+
async function selectNext(model2, from, result, state, deps) {
|
|
3601
5321
|
if (result.kind === "goto") return result.to;
|
|
3602
5322
|
if (result.kind === "take") {
|
|
3603
|
-
const target =
|
|
5323
|
+
const target = model2.selectByPort(from, result.port);
|
|
3604
5324
|
if (target !== null) return target;
|
|
3605
|
-
const def =
|
|
5325
|
+
const def = model2.out(from).find((e) => (!e.condition || !e.condition.trim()) && !e.sourceHandle);
|
|
3606
5326
|
return def ? def.to : null;
|
|
3607
5327
|
}
|
|
3608
|
-
return selectEdge(
|
|
5328
|
+
return selectEdge(model2.out(from), {
|
|
3609
5329
|
slots: deps.processRt.getData(),
|
|
3610
5330
|
tools: state.toolResults,
|
|
3611
5331
|
nodeResults: state.nodeResults,
|
|
@@ -3614,10 +5334,10 @@ async function selectNext(model, from, result, state, deps) {
|
|
|
3614
5334
|
});
|
|
3615
5335
|
}
|
|
3616
5336
|
async function runFlowGraph(program, deps) {
|
|
3617
|
-
const
|
|
5337
|
+
const model2 = buildGraphModel(program);
|
|
3618
5338
|
const maxNodeVisits = deps.maxNodeVisits ?? DEFAULT_MAX_NODE_VISITS;
|
|
3619
5339
|
const maxSteps = deps.maxSteps ?? DEFAULT_MAX_STEPS;
|
|
3620
|
-
let cursor = pickEntry(
|
|
5340
|
+
let cursor = pickEntry(model2);
|
|
3621
5341
|
if (cursor === null) return { kind: "completed" };
|
|
3622
5342
|
const state = { toolResults: {}, nodeResults: {}, cursor };
|
|
3623
5343
|
const visits = /* @__PURE__ */ new Map();
|
|
@@ -3627,7 +5347,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3627
5347
|
deps.events.note("flow.loop_guard_tripped", { reason: "max_steps", steps });
|
|
3628
5348
|
break;
|
|
3629
5349
|
}
|
|
3630
|
-
const node =
|
|
5350
|
+
const node = model2.node(cursor);
|
|
3631
5351
|
if (!node) {
|
|
3632
5352
|
deps.events.note("flow.missing_node", { nodeId: cursor });
|
|
3633
5353
|
break;
|
|
@@ -3641,7 +5361,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3641
5361
|
}
|
|
3642
5362
|
state.cursor = cursor;
|
|
3643
5363
|
deps.events.enter(node.id, node.type);
|
|
3644
|
-
|
|
5364
|
+
graphLog("enter", { node: node.id, type: node.type, step: steps });
|
|
3645
5365
|
const executor = EXECUTORS[node.type] ?? passthroughExecutor;
|
|
3646
5366
|
let result;
|
|
3647
5367
|
try {
|
|
@@ -3650,7 +5370,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3650
5370
|
processRt: deps.processRt,
|
|
3651
5371
|
utterances: deps.utterances,
|
|
3652
5372
|
events: deps.events,
|
|
3653
|
-
model,
|
|
5373
|
+
model: model2,
|
|
3654
5374
|
...deps.history ? { history: deps.history } : {},
|
|
3655
5375
|
...deps.realtime ? { realtime: true } : {},
|
|
3656
5376
|
...deps.resolvers ? { resolvers: deps.resolvers } : {},
|
|
@@ -3664,17 +5384,19 @@ async function runFlowGraph(program, deps) {
|
|
|
3664
5384
|
return { kind: "ended", nodeId: node.id };
|
|
3665
5385
|
}
|
|
3666
5386
|
deps.events.note("node.errored", { nodeId: node.id, error: message });
|
|
3667
|
-
const recovery =
|
|
5387
|
+
const recovery = model2.selectByPort(node.id, "error");
|
|
3668
5388
|
if (recovery !== null) {
|
|
3669
5389
|
deps.events.traverse(node.id, recovery);
|
|
3670
5390
|
cursor = recovery;
|
|
3671
5391
|
continue;
|
|
3672
5392
|
}
|
|
3673
5393
|
return { kind: "error", nodeId: node.id, message };
|
|
5394
|
+
} finally {
|
|
5395
|
+
await deps.utterances.settle?.();
|
|
3674
5396
|
}
|
|
3675
5397
|
if (result.kind === "end") return { kind: "ended", nodeId: node.id };
|
|
3676
5398
|
if (result.kind === "handoff") return { kind: "handoff", to: result.to };
|
|
3677
|
-
const next = await selectNext(
|
|
5399
|
+
const next = await selectNext(model2, cursor, result, state, deps);
|
|
3678
5400
|
if (next !== null) deps.events.traverse(cursor, next);
|
|
3679
5401
|
cursor = next;
|
|
3680
5402
|
}
|
|
@@ -3683,6 +5405,7 @@ async function runFlowGraph(program, deps) {
|
|
|
3683
5405
|
var DEFAULT_MAX_NODE_VISITS, DEFAULT_MAX_STEPS, HUB_MAX_NODE_VISITS;
|
|
3684
5406
|
var init_interpreter = __esm({
|
|
3685
5407
|
"src/runtime/graph/interpreter.ts"() {
|
|
5408
|
+
init_log();
|
|
3686
5409
|
init_graph_model();
|
|
3687
5410
|
init_executors();
|
|
3688
5411
|
init_conditions();
|
|
@@ -3693,15 +5416,33 @@ var init_interpreter = __esm({
|
|
|
3693
5416
|
});
|
|
3694
5417
|
|
|
3695
5418
|
// src/runtime/graph/index.ts
|
|
5419
|
+
init_log();
|
|
3696
5420
|
init_interpreter();
|
|
3697
5421
|
|
|
3698
5422
|
// src/runtime/graph/utterances.ts
|
|
5423
|
+
init_log();
|
|
3699
5424
|
var EVENT_UTTERANCE = "user_input_transcribed";
|
|
3700
5425
|
var EVENT_CLOSE = "close";
|
|
3701
|
-
|
|
5426
|
+
var DEFAULT_MIN_DELAY_MS = 600;
|
|
5427
|
+
var DEFAULT_MAX_DELAY_MS = 3e3;
|
|
5428
|
+
var VAD_SILENCE_MS = 550;
|
|
5429
|
+
var STT_LAG_MS = 400;
|
|
5430
|
+
function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtmf, onAwaitInput, endpointing) {
|
|
3702
5431
|
const queue = [];
|
|
3703
5432
|
let waiter = null;
|
|
3704
5433
|
let hung = false;
|
|
5434
|
+
let pending = null;
|
|
5435
|
+
const capture = async (text, deferred) => {
|
|
5436
|
+
const t0 = Date.now();
|
|
5437
|
+
try {
|
|
5438
|
+
await processRt.ingest(text, ctx);
|
|
5439
|
+
} catch {
|
|
5440
|
+
}
|
|
5441
|
+
if (deferred) graphLog("capture", { deferred: true, ms: Date.now() - t0 });
|
|
5442
|
+
};
|
|
5443
|
+
const settle = async () => {
|
|
5444
|
+
if (pending) await pending;
|
|
5445
|
+
};
|
|
3705
5446
|
const deliver = (e) => {
|
|
3706
5447
|
if (hung && e.kind !== "hangup") return;
|
|
3707
5448
|
if (waiter) {
|
|
@@ -3722,13 +5463,72 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3722
5463
|
};
|
|
3723
5464
|
const unsubs = [];
|
|
3724
5465
|
if (ctx.adapter) {
|
|
5466
|
+
let onSettle2 = function() {
|
|
5467
|
+
settle2 = null;
|
|
5468
|
+
settleDue = Number.POSITIVE_INFINITY;
|
|
5469
|
+
const idle = Date.now() - lastActivity;
|
|
5470
|
+
if (speaking && idle < minDelay + STT_LAG_MS) settleAt(minDelay + STT_LAG_MS - idle);
|
|
5471
|
+
else flush();
|
|
5472
|
+
};
|
|
3725
5473
|
const a = ctx.adapter;
|
|
5474
|
+
const voice2 = a.capabilities?.channel === "voice";
|
|
5475
|
+
const minDelay = Math.max(0, endpointing?.minDelay ?? DEFAULT_MIN_DELAY_MS);
|
|
5476
|
+
const maxDelay = Math.max(minDelay, endpointing?.maxDelay ?? DEFAULT_MAX_DELAY_MS);
|
|
5477
|
+
const parts = [];
|
|
5478
|
+
let speaking = false;
|
|
5479
|
+
let lastActivity = 0;
|
|
5480
|
+
let settle2 = null;
|
|
5481
|
+
let settleDue = Number.POSITIVE_INFINITY;
|
|
5482
|
+
let cap = null;
|
|
5483
|
+
const clearTimers = () => {
|
|
5484
|
+
if (settle2) clearTimeout(settle2);
|
|
5485
|
+
if (cap) clearTimeout(cap);
|
|
5486
|
+
settle2 = null;
|
|
5487
|
+
cap = null;
|
|
5488
|
+
settleDue = Number.POSITIVE_INFINITY;
|
|
5489
|
+
};
|
|
5490
|
+
const flush = () => {
|
|
5491
|
+
clearTimers();
|
|
5492
|
+
const text = parts.splice(0).join(" ").trim();
|
|
5493
|
+
if (!text) return;
|
|
5494
|
+
graphLog("turn", { chars: text.length, quietMs: Date.now() - lastActivity, vadSpeaking: speaking });
|
|
5495
|
+
deliver({ kind: "utterance", text });
|
|
5496
|
+
};
|
|
5497
|
+
const settleAt = (ms) => {
|
|
5498
|
+
if (settle2) clearTimeout(settle2);
|
|
5499
|
+
const wait = Math.max(0, ms);
|
|
5500
|
+
settleDue = Date.now() + wait;
|
|
5501
|
+
settle2 = setTimeout(onSettle2, wait);
|
|
5502
|
+
};
|
|
3726
5503
|
unsubs.push(
|
|
3727
5504
|
a.on("user_input", (e) => {
|
|
3728
|
-
if (
|
|
3729
|
-
|
|
5505
|
+
if (hung) return;
|
|
5506
|
+
lastActivity = Date.now();
|
|
5507
|
+
if (!e.isFinal) {
|
|
5508
|
+
if (parts.length > 0) settleAt(minDelay);
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
const text = e.transcript.trim();
|
|
5512
|
+
if (!text) return;
|
|
5513
|
+
if (!voice2 || minDelay === 0) {
|
|
5514
|
+
deliver({ kind: "utterance", text });
|
|
5515
|
+
return;
|
|
5516
|
+
}
|
|
5517
|
+
parts.push(text);
|
|
5518
|
+
if (cap) clearTimeout(cap);
|
|
5519
|
+
cap = setTimeout(flush, maxDelay);
|
|
5520
|
+
settleAt(minDelay);
|
|
5521
|
+
})
|
|
5522
|
+
);
|
|
5523
|
+
unsubs.push(
|
|
5524
|
+
a.on("user_state", (e) => {
|
|
5525
|
+
speaking = e.speaking;
|
|
5526
|
+
if (speaking || parts.length === 0) return;
|
|
5527
|
+
const due = e.at - VAD_SILENCE_MS + minDelay;
|
|
5528
|
+
if (due < settleDue) settleAt(due - Date.now());
|
|
3730
5529
|
})
|
|
3731
5530
|
);
|
|
5531
|
+
unsubs.push(clearTimers);
|
|
3732
5532
|
unsubs.push(a.once("close", onClose));
|
|
3733
5533
|
if (wantsDtmf) {
|
|
3734
5534
|
if (typeof ctx.onDtmf === "function") {
|
|
@@ -3796,10 +5596,12 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3796
5596
|
} else if (hung) {
|
|
3797
5597
|
return { kind: "hangup" };
|
|
3798
5598
|
} else {
|
|
5599
|
+
if (onAwaitInput) onAwaitInput();
|
|
3799
5600
|
raw = await awaitRaw(opts?.timeoutMs);
|
|
3800
5601
|
}
|
|
3801
5602
|
if (raw.kind === "hangup") return { kind: "hangup" };
|
|
3802
5603
|
if (raw.kind === "timeout") return { kind: "timeout" };
|
|
5604
|
+
await settle();
|
|
3803
5605
|
if (raw.kind === "dtmf") return { kind: "dtmf", digit: raw.digit };
|
|
3804
5606
|
if (rails) {
|
|
3805
5607
|
const outcome = await rails.intercept(raw.text, ctx);
|
|
@@ -3807,13 +5609,18 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3807
5609
|
if (outcome.kind === "control") return { kind: "control", result: outcome.result };
|
|
3808
5610
|
}
|
|
3809
5611
|
if (prewarm) prewarm(raw.text);
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
5612
|
+
if (opts?.deferCapture) {
|
|
5613
|
+
const run = capture(raw.text, true).then(() => {
|
|
5614
|
+
if (pending === run) pending = null;
|
|
5615
|
+
});
|
|
5616
|
+
pending = run;
|
|
5617
|
+
return { kind: "utterance", text: raw.text };
|
|
3813
5618
|
}
|
|
5619
|
+
await capture(raw.text, false);
|
|
3814
5620
|
return { kind: "utterance", text: raw.text };
|
|
3815
5621
|
}
|
|
3816
5622
|
},
|
|
5623
|
+
settle,
|
|
3817
5624
|
close() {
|
|
3818
5625
|
hung = true;
|
|
3819
5626
|
for (const u of unsubs) u();
|
|
@@ -3822,6 +5629,7 @@ function createUtteranceStream(session, processRt, ctx, rails, prewarm, wantsDtm
|
|
|
3822
5629
|
}
|
|
3823
5630
|
|
|
3824
5631
|
// src/runtime/graph/history.ts
|
|
5632
|
+
init_log();
|
|
3825
5633
|
function isConversational(it) {
|
|
3826
5634
|
return it.type === "message" && (it.role === "user" || it.role === "assistant");
|
|
3827
5635
|
}
|
|
@@ -3861,7 +5669,7 @@ function createHistoryController(agent) {
|
|
|
3861
5669
|
next.items = prune(next.items, policy);
|
|
3862
5670
|
await agent.updateChatCtx(next);
|
|
3863
5671
|
} catch (err) {
|
|
3864
|
-
|
|
5672
|
+
graphWarn("historyPolicy prune failed; using full history", {
|
|
3865
5673
|
policy,
|
|
3866
5674
|
err: err instanceof Error ? err.message : String(err)
|
|
3867
5675
|
});
|
|
@@ -3874,7 +5682,7 @@ function createHistoryController(agent) {
|
|
|
3874
5682
|
try {
|
|
3875
5683
|
await agent.updateChatCtx(saved);
|
|
3876
5684
|
} catch (err) {
|
|
3877
|
-
|
|
5685
|
+
graphWarn("historyPolicy restore failed", {
|
|
3878
5686
|
err: err instanceof Error ? err.message : String(err)
|
|
3879
5687
|
});
|
|
3880
5688
|
}
|
|
@@ -3911,13 +5719,17 @@ function createGraphEvents(processRt) {
|
|
|
3911
5719
|
};
|
|
3912
5720
|
}
|
|
3913
5721
|
|
|
5722
|
+
// src/runtime/graph/rails.ts
|
|
5723
|
+
init_log();
|
|
5724
|
+
|
|
3914
5725
|
// src/runtime/graph/index.ts
|
|
3915
5726
|
init_conditions();
|
|
3916
5727
|
init_branch_resolve();
|
|
3917
5728
|
init_embedder();
|
|
3918
5729
|
|
|
3919
5730
|
// src/runtime/graph/agent-runner.ts
|
|
3920
|
-
|
|
5731
|
+
init_log();
|
|
5732
|
+
init_llm();
|
|
3921
5733
|
process.env["VOICELAYER_LLM_MODEL"] ?? process.env["OPENAI_LLM_MODEL"] ?? "gpt-4o";
|
|
3922
5734
|
Number(process.env["VL_FLOW_AGENT_TURN_TIMEOUT_MS"] ?? "30000");
|
|
3923
5735
|
Number(process.env["VL_FLOW_AGENT_MAX_TOOL_CALLS"] ?? "5");
|
|
@@ -3930,7 +5742,7 @@ init_graph_model();
|
|
|
3930
5742
|
// src/runtime/graph/resolvers.ts
|
|
3931
5743
|
init_src();
|
|
3932
5744
|
init_embedder();
|
|
3933
|
-
|
|
5745
|
+
init_llm();
|
|
3934
5746
|
Number(process.env["VL_FLOW_CODE_TIMEOUT_MS"] ?? "1000");
|
|
3935
5747
|
Number(process.env["VL_FLOW_CODE_MEMORY_BYTES"] ?? String(16 * 1024 * 1024));
|
|
3936
5748
|
|
|
@@ -3951,7 +5763,9 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
3951
5763
|
ctx,
|
|
3952
5764
|
opts.rails,
|
|
3953
5765
|
prewarm,
|
|
3954
|
-
wantsDtmf
|
|
5766
|
+
wantsDtmf,
|
|
5767
|
+
opts.onAwaitInput,
|
|
5768
|
+
opts.speech?.endpointing
|
|
3955
5769
|
);
|
|
3956
5770
|
const history = createHistoryController(opts.agent ?? null);
|
|
3957
5771
|
try {
|
|
@@ -3967,15 +5781,16 @@ async function runFlowProgram(program, ctx, opts) {
|
|
|
3967
5781
|
...opts.resolvers ? { resolvers: opts.resolvers } : {},
|
|
3968
5782
|
...opts.busy ? { busy: opts.busy } : {}
|
|
3969
5783
|
});
|
|
3970
|
-
|
|
5784
|
+
graphLog("outcome", outcome);
|
|
3971
5785
|
return outcome;
|
|
3972
5786
|
} finally {
|
|
5787
|
+
await utterances.settle?.();
|
|
3973
5788
|
utterances.close();
|
|
3974
5789
|
}
|
|
3975
5790
|
}
|
|
3976
5791
|
|
|
3977
5792
|
// src/runtime/process.ts
|
|
3978
|
-
|
|
5793
|
+
init_llm();
|
|
3979
5794
|
|
|
3980
5795
|
// ../primitives/process-schema/src/index.ts
|
|
3981
5796
|
init_src();
|
|
@@ -4871,6 +6686,10 @@ function createTextSessionAdapter(transport) {
|
|
|
4871
6686
|
await transport.send(text);
|
|
4872
6687
|
return { interrupted: false };
|
|
4873
6688
|
},
|
|
6689
|
+
async enqueueSay(text) {
|
|
6690
|
+
fire("agent_turn", { text });
|
|
6691
|
+
await transport.send(text);
|
|
6692
|
+
},
|
|
4874
6693
|
async generateReply(opts) {
|
|
4875
6694
|
if (transport.generate) {
|
|
4876
6695
|
const text = await transport.generate(opts?.instructions);
|
|
@@ -4949,6 +6768,7 @@ function processSchemaToDefinition(schema) {
|
|
|
4949
6768
|
}
|
|
4950
6769
|
|
|
4951
6770
|
// src/runtime/text-session.ts
|
|
6771
|
+
init_openai_default();
|
|
4952
6772
|
function buildTextContext(adapter, call, processRt) {
|
|
4953
6773
|
return {
|
|
4954
6774
|
call: {
|
|
@@ -5010,7 +6830,8 @@ function runTextSession(opts) {
|
|
|
5010
6830
|
// createUtteranceStream prefers ctx.adapter; the session arg is unused here.
|
|
5011
6831
|
session: {},
|
|
5012
6832
|
events,
|
|
5013
|
-
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {}
|
|
6833
|
+
...opts.maxSteps !== void 0 ? { maxSteps: opts.maxSteps } : {},
|
|
6834
|
+
...opts.onAwaitInput ? { onAwaitInput: opts.onAwaitInput } : {}
|
|
5014
6835
|
});
|
|
5015
6836
|
return {
|
|
5016
6837
|
sendUserMessage: (text) => handle.push(text),
|
|
@@ -5056,12 +6877,12 @@ function definitionFor(opts) {
|
|
|
5056
6877
|
}
|
|
5057
6878
|
async function runTextTranscript(opts) {
|
|
5058
6879
|
const replies = [];
|
|
5059
|
-
const
|
|
6880
|
+
const trace2 = [];
|
|
5060
6881
|
const processRt = opts.processRt ?? createProcessRuntime(definitionFor(opts));
|
|
5061
6882
|
const session = runTextSession({
|
|
5062
6883
|
program: opts.program,
|
|
5063
6884
|
onAgentText: (t) => void replies.push(t),
|
|
5064
|
-
onTrace: (e) => void
|
|
6885
|
+
onTrace: (e) => void trace2.push(e),
|
|
5065
6886
|
...processRt ? { processRt } : {},
|
|
5066
6887
|
...opts.generate ? { generate: opts.generate } : {},
|
|
5067
6888
|
...opts.call ? { call: opts.call } : {},
|
|
@@ -5070,7 +6891,57 @@ async function runTextTranscript(opts) {
|
|
|
5070
6891
|
for (const m of opts.messages) session.sendUserMessage(m);
|
|
5071
6892
|
session.end();
|
|
5072
6893
|
const outcome = await session.result;
|
|
5073
|
-
return { replies, outcome, trace };
|
|
6894
|
+
return { replies, outcome, trace: trace2 };
|
|
6895
|
+
}
|
|
6896
|
+
function runLiveTextConversation(opts) {
|
|
6897
|
+
let buffer = [];
|
|
6898
|
+
let settle = null;
|
|
6899
|
+
let parkedPending = false;
|
|
6900
|
+
let ended = false;
|
|
6901
|
+
let outcome;
|
|
6902
|
+
const signal = () => {
|
|
6903
|
+
if (settle) {
|
|
6904
|
+
const s = settle;
|
|
6905
|
+
settle = null;
|
|
6906
|
+
s();
|
|
6907
|
+
} else {
|
|
6908
|
+
parkedPending = true;
|
|
6909
|
+
}
|
|
6910
|
+
};
|
|
6911
|
+
const processRt = opts.processRt ?? createProcessRuntime(definitionFor({ program: opts.program, ...opts.schema ? { schema: opts.schema } : {}, messages: [] }));
|
|
6912
|
+
const session = runTextSession({
|
|
6913
|
+
...opts,
|
|
6914
|
+
processRt,
|
|
6915
|
+
onAgentText: (t) => {
|
|
6916
|
+
buffer.push(t);
|
|
6917
|
+
return opts.onAgentText?.(t);
|
|
6918
|
+
},
|
|
6919
|
+
onAwaitInput: signal
|
|
6920
|
+
});
|
|
6921
|
+
void session.result.then((o) => {
|
|
6922
|
+
outcome = o;
|
|
6923
|
+
ended = true;
|
|
6924
|
+
signal();
|
|
6925
|
+
});
|
|
6926
|
+
const collect = async () => {
|
|
6927
|
+
if (!parkedPending) await new Promise((res) => settle = res);
|
|
6928
|
+
parkedPending = false;
|
|
6929
|
+
const replies = buffer.slice();
|
|
6930
|
+
buffer = [];
|
|
6931
|
+
return { replies, done: ended, ...ended && outcome ? { outcome } : {} };
|
|
6932
|
+
};
|
|
6933
|
+
return {
|
|
6934
|
+
start: () => collect(),
|
|
6935
|
+
async turn(text) {
|
|
6936
|
+
if (ended) return { replies: [], done: true, ...outcome ? { outcome } : {} };
|
|
6937
|
+
session.sendUserMessage(text);
|
|
6938
|
+
return collect();
|
|
6939
|
+
},
|
|
6940
|
+
async end() {
|
|
6941
|
+
session.end();
|
|
6942
|
+
return session.result;
|
|
6943
|
+
}
|
|
6944
|
+
};
|
|
5074
6945
|
}
|
|
5075
6946
|
|
|
5076
|
-
export { runTextSession, runTextTranscript };
|
|
6947
|
+
export { runLiveTextConversation, runTextSession, runTextTranscript, runWithOpenAIScope };
|