@voicelayer/sdk 0.4.0 → 0.4.2

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 CHANGED
@@ -32,7 +32,9 @@ export default await defineAgent({
32
32
  node --import tsx agent.ts dev
33
33
  ```
34
34
 
35
- Call your LiveKit number. The agent answers. That's it. STT, LLM, TTS, VAD, turn detection — defaults are picked for you. Swap them later with one config line.
35
+ The worker runs on **your** LiveKit project. Call a number you have connected to that project (a LiveKit SIP trunk plus a dispatch rule for the agent name `hello`), or join a room from LiveKit's Agents Playground, and the agent answers. STT, LLM, TTS, VAD, turn detection — defaults are picked for you. Swap them later with one config line.
36
+
37
+ > **What a self-hosted worker can and can't do today.** A number bought or imported in VoiceLayer can't reach a worker you run on your own LiveKit project: VoiceLayer numbers answer with **hosted flow agents** (built in the dashboard's flow builder) and **brain-connector agents** (your own LLM behind a VoiceLayer-hosted voice pipeline — see [Bring your own LLM](#bring-your-own-llm)). Your worker still registers with VoiceLayer, so it shows up in the dashboard with its calls and transcripts.
36
38
 
37
39
  ---
38
40
 
@@ -301,7 +303,7 @@ Each method receives `(args, ctx)`, so you can reach call state from inside a co
301
303
  node --import tsx agent.ts dev
302
304
  ```
303
305
 
304
- This boots the worker against your LiveKit project and registers the agent with the VoiceLayer control plane, so you can call your number and iterate on the same file you will deploy. Set the environment variables listed under [Production](#production) first.
306
+ This boots the worker against your LiveKit project and registers the agent with the VoiceLayer control plane, so you can call a number connected to your LiveKit project and iterate on the same file you will deploy. Set the environment variables listed under [Production](#production) first.
305
307
 
306
308
  > A headless harness for asserting on process capture without audio is not part of the public API yet. Today, iterate by calling the agent, or drive the flow from the Playground in the dashboard.
307
309
 
@@ -320,21 +322,28 @@ export LIVEKIT_URL=...
320
322
  export LIVEKIT_API_KEY=...
321
323
  export LIVEKIT_API_SECRET=...
322
324
 
323
- # Provider keys for the models your agent uses (or set them per project in the
324
- # VoiceLayer dashboard's connections, resolved at call time):
325
+ # Required. Provider keys for the models your agent uses. Your worker reads
326
+ # them from its own environment — provider keys saved in the VoiceLayer
327
+ # dashboard's Connections are NOT handed to a worker you run yourself.
325
328
  export OPENAI_API_KEY=...
326
329
  export DEEPGRAM_API_KEY=...
327
330
 
328
331
  node agent.ts start
329
332
  ```
330
333
 
334
+ Agent names starting `voicelayer-` (and the names of VoiceLayer's own workers) are reserved; registering one is refused with `422 agent_name_reserved`.
335
+
331
336
  ### Texts
332
337
 
333
- The same worker answers texts — SMS replies, Discord, your website's chat widget, the dashboard playground. `.start()`
334
- registers a second LiveKit name, `<name>::text`, beside the voice one; each message is dispatched to it and runs as
335
- your agent: its tools, security, connectors and model, with the conversation so far. Over text `ctx.call.callerId` is
336
- the sender, `ctx.call.metadata.mode` is `'text'`, and call-only methods (`handoff`, `endCall`, `ask`, DTMF) throw. In
337
- production the text registration's health server listens on `VL_TEXT_TURN_PORT` (default 8082).
338
+ `.start()` also registers a second LiveKit name, `<name>::text`, beside the voice one. A text message dispatched to it
339
+ runs as your agent: its tools, security, connectors and model, with the conversation so far. Over text
340
+ `ctx.call.callerId` is the sender, `ctx.call.metadata.mode` is `'text'`, and call-only methods (`handoff`, `endCall`,
341
+ `ask`, DTMF) throw. In production the text registration's health server listens on `VL_TEXT_TURN_PORT` (default 8082).
342
+
343
+ > **Today** VoiceLayer dispatches text turns (SMS replies, Discord, the website chat widget, the dashboard playground)
344
+ > through its own LiveKit project, so they reach VoiceLayer-hosted workers only. A worker on your own LiveKit project
345
+ > doesn't receive them yet — the turn answers `agent_offline`. For texts on a VoiceLayer number, use a hosted flow
346
+ > agent or a brain-connector agent.
338
347
 
339
348
  ---
340
349
 
@@ -79,14 +79,14 @@ declare const ConnectorUpFrame: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
79
79
  code: z.ZodEnum<["upstream_timeout", "upstream_error", "bad_response", "normalize_failed", "unreachable"]>;
80
80
  message: z.ZodString;
81
81
  }, "strip", z.ZodTypeAny, {
82
+ kind: "brain.error";
82
83
  code: "upstream_timeout" | "upstream_error" | "bad_response" | "normalize_failed" | "unreachable";
83
84
  message: string;
84
- kind: "brain.error";
85
85
  streamId: string;
86
86
  }, {
87
+ kind: "brain.error";
87
88
  code: "upstream_timeout" | "upstream_error" | "bad_response" | "normalize_failed" | "unreachable";
88
89
  message: string;
89
- kind: "brain.error";
90
90
  streamId: string;
91
91
  }>]>;
92
92
  type ConnectorUpFrame = z.infer<typeof ConnectorUpFrame>;
@@ -622,9 +622,11 @@ var ProcessToolDTO = z.object({
622
622
  });
623
623
  var ProcessSchemaDTO = z.object({
624
624
  id: z.string().min(1).max(64),
625
- // System-prompt fragments compiled from say/confirm nodes. The SDK bridge
626
- // composes these into the agent's prompt.
627
- prompts: z.array(z.object({ id: z.string().min(1).max(128), text: z.string().max(2e3) })).max(64).optional(),
625
+ // System-prompt fragments compiled from say/confirm/llm nodes. The SDK bridge
626
+ // composes these into the agent's prompt. An AI step's instructions are a
627
+ // system prompt in their own right, so the cap is generous (2,000 refused real
628
+ // flows — G-27); publish checks it (validateFlowGraph → compiledSchemaIssues).
629
+ prompts: z.array(z.object({ id: z.string().min(1).max(128), text: z.string().max(16e3) })).max(64).optional(),
628
630
  fields: z.array(ProcessFieldDTO).max(64),
629
631
  completionGate: z.object({
630
632
  requiredFields: z.array(z.string()).max(64),
@@ -1780,6 +1782,41 @@ z.object({
1780
1782
  toolCalls: z.array(ToolCallAudit),
1781
1783
  mcpInteractions: z.array(McpInteractionAudit)
1782
1784
  });
1785
+ var WEBHOOK_EVENT_TYPES = ["call.started", "call.ended", "recording.ready"];
1786
+ z.enum(WEBHOOK_EVENT_TYPES);
1787
+ var CallWebhookDirection = z.enum(["inbound", "outbound", "web"]);
1788
+ var CallWebhookOutcome = z.enum(["completed", "dropped", "failed", "no_answer", "other"]);
1789
+ var CallWebhookAttributes = z.record(z.union([z.string(), z.number(), z.boolean()]));
1790
+ var Party = z.string().min(1).nullable();
1791
+ z.object({
1792
+ callId: z.string().uuid(),
1793
+ startedAt: z.string().datetime(),
1794
+ agentId: z.string().nullable(),
1795
+ phoneNumberId: z.string().nullable(),
1796
+ callerE164: Party,
1797
+ toE164: Party,
1798
+ // Null when it can't be told yet: an inbound phone call and a web session look alike until the caller joins. When
1799
+ // set, it is the same value `call.ended` carries; `call.ended` always has one.
1800
+ direction: CallWebhookDirection.nullable()
1801
+ });
1802
+ z.object({
1803
+ callId: z.string().uuid(),
1804
+ durationMs: z.number().int().min(0),
1805
+ endedAt: z.string().datetime(),
1806
+ endReason: z.string(),
1807
+ agentId: z.string().nullable(),
1808
+ phoneNumberId: z.string().nullable(),
1809
+ callerE164: Party,
1810
+ toE164: Party,
1811
+ direction: CallWebhookDirection,
1812
+ outcome: CallWebhookOutcome,
1813
+ attributes: CallWebhookAttributes
1814
+ });
1815
+ z.object({
1816
+ callId: z.string().uuid(),
1817
+ recordingUrl: z.string().url(),
1818
+ status: z.enum(["starting", "active", "completed", "failed", "aborted"])
1819
+ });
1783
1820
  var PhoneNumberStatus = z.enum([
1784
1821
  "pending",
1785
1822
  "active",
@@ -1984,6 +2021,39 @@ byokProviders().map((p) => ({
1984
2021
  keyPlaceholder: p.keyPlaceholder ?? "",
1985
2022
  supportsBaseUrl: p.supportsBaseUrl
1986
2023
  }));
2024
+ z.object({
2025
+ id: z.string().min(1),
2026
+ label: z.string().min(1),
2027
+ /** The vault `provider` the resulting connection is stored under. */
2028
+ vaultProvider: z.string().min(1),
2029
+ category: z.enum(["model", "tool"])
2030
+ });
2031
+ z.object({
2032
+ authorizeUrl: z.string().url()
2033
+ });
2034
+ z.object({
2035
+ state: z.string().min(1).max(512),
2036
+ code: z.string().min(1).max(4096)
2037
+ });
2038
+ z.object({
2039
+ providerId: z.string().min(1),
2040
+ /** The provider's display name ("ChatGPT"). */
2041
+ providerLabel: z.string().min(1),
2042
+ /** What the connection is for — which group the dashboard files it under. */
2043
+ category: z.enum(["model", "tool"]),
2044
+ /** The account's stable id at the provider (OIDC `sub`); reconnecting the same account updates the same row. */
2045
+ subject: z.string().min(1).nullable(),
2046
+ email: z.string().nullable(),
2047
+ displayName: z.string().nullable(),
2048
+ scopes: z.array(z.string()),
2049
+ /**
2050
+ * The workspace user who signed in. A person's model plan (category 'model') serves only them — never teammates or
2051
+ * API keys. Absent on rows connected before it was recorded.
2052
+ */
2053
+ connectedBy: z.string().nullable().optional(),
2054
+ /** Set when a refresh was refused (e.g. `invalid_grant`): the owner must connect again. */
2055
+ reauthRequired: z.boolean().optional()
2056
+ });
1987
2057
  var E164 = z.string().regex(/^\+[1-9]\d{6,14}$/, "must be E.164 (+15551234567)");
1988
2058
  var RoomPrefix = z.string().regex(/^[a-z0-9][a-z0-9-]{0,30}[a-z0-9-]?$/, "lowercase letters, digits, dashes; max 32");
1989
2059
  var RecordingPrefix = z.string().min(1).max(128).refine((v) => !v.startsWith("/") && !v.includes(".."), "relative key prefix only");
@@ -2286,6 +2356,10 @@ z.discriminatedUnion("kind", [
2286
2356
  BrainRequestFrame,
2287
2357
  BrainCancelFrame
2288
2358
  ]);
2359
+ var FLOW_BOOT_FAILURES = ["schema_fetch_failed", "no_process_schema", "missing_agent_id", "no_api_key"];
2360
+ z.enum(
2361
+ FLOW_BOOT_FAILURES.map((cause) => `flow_boot:${cause}`)
2362
+ );
2289
2363
  var EnvironmentSpec = z.enum(["draft", "live"]);
2290
2364
  z.object({
2291
2365
  agentName: z.string().min(1),