@voicelayer/sdk 0.6.0 → 0.6.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.
@@ -1,4 +1,4 @@
1
- export { b1 as EmailResolver, b2 as FlowResolver, b3 as GraphResolvers, b4 as GraphTraceEvent, b5 as KnowledgeResolver, b6 as LiveTextConversation, b7 as LiveTurnResult, b8 as PlaybookLibraryEntry, b9 as PlaybookResolver, ba as RegistryToolsResolver, bb as RunLiveTextOptions, ag as RunTextSessionOptions, ah as RunTextTranscriptOptions, ay as TextSession, az as TextTranscriptResult, n as ToolExecutor, bc as dedupeConversationEmails, bd as denyPrivateNetwork, be as privateNetworkOptInHonoured, bf as runLiveTextConversation, aZ as runTextSession, a_ as runTextTranscript, bg as runWithOpenAIScope, bh as toPlaybookResolution } from '../text-session-CoyEcdmg.js';
1
+ export { b1 as EmailResolver, b2 as FlowResolver, b3 as GraphResolvers, b4 as GraphTraceEvent, b5 as KnowledgeResolver, b6 as LiveTextConversation, b7 as LiveTurnResult, b8 as PlaybookLibraryEntry, b9 as PlaybookResolver, ba as RegistryToolsResolver, bb as RunLiveTextOptions, ag as RunTextSessionOptions, ah as RunTextTranscriptOptions, ay as TextSession, az as TextTranscriptResult, n as ToolExecutor, bc as dedupeConversationEmails, bd as denyPrivateNetwork, be as privateNetworkOptInHonoured, bf as runLiveTextConversation, aZ as runTextSession, a_ as runTextTranscript, bg as runWithOpenAIScope, bh as toPlaybookResolution } from '../text-session-B-T33y5P.js';
2
2
  import '@livekit/agents';
3
3
  import 'zod';
4
4
  import '../types-KqrAfY85.js';
@@ -693,7 +693,7 @@ var init_consultation = __esm({
693
693
  });
694
694
  }
695
695
  });
696
- var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, commandId, SayCommand, HangupCommand, DtmfCommand, InstructCommand, InjectContextCommand;
696
+ var CallEventKind, OP_CALL_EVENT_KIND, OpCallEventKind, LatencyMs, commandId, SayCommand, HangupCommand, DtmfCommand, InstructCommand, InjectContextCommand;
697
697
  var init_call_events = __esm({
698
698
  "../contracts/src/call-events.ts"() {
699
699
  init_consultation();
@@ -719,7 +719,9 @@ var init_call_events = __esm({
719
719
  "dtmf.sent",
720
720
  // A host's runtime intervention (burn-down G-6): call_say / call_send_guidance / call_inject_context / call_instruct,
721
721
  // with masked args and the API key — written by the API only, never through the worker's /events endpoint.
722
- "mcp.interaction"
722
+ "mcp.interaction",
723
+ // One per caller turn the agent answered (burn-down G-44): where that turn's wait went — see TurnLatencyPayload.
724
+ "turn.latency"
723
725
  ]);
724
726
  OP_CALL_EVENT_KIND = /^(tool|handoff|lookup|record|notify|engine)\.[a-z0-9_]{1,48}(\.[a-z0-9_]{1,48})?$/;
725
727
  OpCallEventKind = z.string().regex(OP_CALL_EVENT_KIND);
@@ -746,6 +748,17 @@ var init_call_events = __esm({
746
748
  code: z.number().int().min(0).max(15),
747
749
  participantId: z.string().optional()
748
750
  });
751
+ LatencyMs = z.number().int().min(0).max(6e5).nullable();
752
+ z.object({
753
+ turn: z.number().int().min(1),
754
+ atMs: z.number().int().min(0),
755
+ endpointingMs: LatencyMs,
756
+ sttMs: LatencyMs,
757
+ llmMs: LatencyMs,
758
+ ttsMs: LatencyMs,
759
+ endToEndMs: LatencyMs,
760
+ realtime: z.boolean().optional()
761
+ });
749
762
  z.object({
750
763
  totalUsd: z.string(),
751
764
  breakdown: z.object({
@@ -862,9 +875,65 @@ var init_text_turns = __esm({
862
875
  ]);
863
876
  }
864
877
  });
878
+ var PLATFORM_MAX_CALL_MINUTES, Message, CallerRateLimit, CallLimits, CallerChannel, CallDurationLimits;
879
+ var init_call_limits = __esm({
880
+ "../contracts/src/call-limits.ts"() {
881
+ PLATFORM_MAX_CALL_MINUTES = 240;
882
+ Message = z.string().trim().min(1).max(300);
883
+ CallerRateLimit = z.object({
884
+ enabled: z.boolean().optional(),
885
+ perTenMinutes: z.number().int().min(1).max(100).optional(),
886
+ perDay: z.number().int().min(1).max(1e3).optional(),
887
+ withheldPerTenMinutes: z.number().int().min(1).max(100).optional(),
888
+ withheldPerDay: z.number().int().min(1).max(1e3).optional()
889
+ });
890
+ CallLimits = z.object({
891
+ /** The agent's own ceiling in minutes; clamped to the workspace's plan ceiling. Absent ⇒ DEFAULT_MAX_CALL_MINUTES. */
892
+ maxDurationMinutes: z.number().int().min(1).max(PLATFORM_MAX_CALL_MINUTES).optional(),
893
+ /** Seconds before the limit the wrap-up line is spoken; 0 ⇒ no warning. */
894
+ wrapUpWarningSeconds: z.number().int().min(0).max(300).optional(),
895
+ wrapUpMessage: Message.optional(),
896
+ goodbyeMessage: Message.optional(),
897
+ callerRateLimit: CallerRateLimit.optional(),
898
+ rateLimitedMessage: Message.optional()
899
+ });
900
+ CallerChannel = z.enum(["phone", "web"]);
901
+ z.object({
902
+ roomName: z.string().min(1).max(256),
903
+ /** From the dispatch metadata: lets the API admit (gate) a room whose call row doesn't exist yet. */
904
+ agentId: z.string().max(128).optional(),
905
+ phoneNumberId: z.string().max(128).optional(),
906
+ caller: z.object({
907
+ channel: CallerChannel,
908
+ /** The caller's number as the carrier presented it; null / absent / not E.164 ⇒ the withheld bucket. */
909
+ number: z.string().max(64).nullable().optional()
910
+ })
911
+ });
912
+ CallDurationLimits = z.object({
913
+ /** The agent's own ceiling (already clamped to the plan), or null when it set none. */
914
+ agentMaxSeconds: z.number().int().min(1).nullable(),
915
+ /** The plan ceiling — the platform bound no call outlives. */
916
+ ceilingSeconds: z.number().int().min(1),
917
+ wrapUpWarningSeconds: z.number().int().min(0),
918
+ wrapUpMessage: z.string(),
919
+ goodbyeMessage: z.string()
920
+ });
921
+ z.discriminatedUnion("admitted", [
922
+ z.object({ admitted: z.literal(true), limits: CallDurationLimits }),
923
+ z.object({
924
+ admitted: z.literal(false),
925
+ /** 'rate_limited:caller' | 'inbound_refused:<code>' — already recorded as the call's end reason. */
926
+ endReason: z.string(),
927
+ /** The fixed line to speak before hanging up. */
928
+ message: z.string()
929
+ })
930
+ ]);
931
+ }
932
+ });
865
933
  var VoiceConfig, ModelConfig, SttConfig, RealtimeConfig, PipelineMode, ToolBinding, ProcessFieldType, ProcessFieldSpec, ProcessCompleteWhen, ProcessBackendAckSpec, ProcessSpec, TriggerConditionSpec, TriggerActionSpec, TriggerSpec, RequiredInfoField, AgentConfig;
866
934
  var init_agent_config = __esm({
867
935
  "../contracts/src/agent-config.ts"() {
936
+ init_call_limits();
868
937
  init_consultation();
869
938
  VoiceConfig = z.object({
870
939
  // Finite, vetted list — platform-level enum.
@@ -1028,6 +1097,9 @@ var init_agent_config = __esm({
1028
1097
  // LLM slot through the connector instead of a first-party model. Null/absent →
1029
1098
  // first-party pipeline (unchanged default). See docs/specs/voice-brain-connector.
1030
1099
  brainConnectorId: z.string().uuid().nullable().optional(),
1100
+ // Per-agent call bounds: caller rate limit, max duration and their spoken lines (burn-down G-41, G-43). Absent ⇒
1101
+ // every default (call-limits.ts) — the rate limit is ON by default.
1102
+ callLimits: CallLimits.optional(),
1031
1103
  // Provenance: 'sdk' (hand-coded defineAgent worker), 'flow' (canvas deploy),
1032
1104
  // 'playbook' (agent-mode deploy), or 'connector' (brain-connector agent).
1033
1105
  // DERIVED from the linked agents row's deploy tags (flowId / playbookId /
@@ -1341,6 +1413,7 @@ var init_agent_versions = __esm({
1341
1413
  "../contracts/src/agent-versions.ts"() {
1342
1414
  init_agent_config();
1343
1415
  init_agents();
1416
+ init_call_limits();
1344
1417
  init_consultation();
1345
1418
  z.object({
1346
1419
  systemPrompt: z.string().max(64e3),
@@ -1355,6 +1428,8 @@ var init_agent_versions = __esm({
1355
1428
  consultation: ConsultationPolicy,
1356
1429
  tags: z.array(z.string().max(64)).max(32).nullable(),
1357
1430
  brainConnectorId: z.string().uuid().nullable(),
1431
+ // Versions published before call limits existed carry none: read as null (every default).
1432
+ callLimits: CallLimits.nullable().default(null),
1358
1433
  flowVersion: z.number().int().nullable(),
1359
1434
  processSchema: ProcessSchemaDTO.nullable()
1360
1435
  });
@@ -1390,6 +1465,7 @@ var init_agent_versions = __esm({
1390
1465
  "language",
1391
1466
  "tools",
1392
1467
  "brainConnectorId",
1468
+ "callLimits",
1393
1469
  "consultation",
1394
1470
  "tags",
1395
1471
  "flowVersion",
@@ -1531,6 +1607,9 @@ var init_model_catalog = __esm({
1531
1607
  CostEstimateBilledPerMinute = z.object({
1532
1608
  platformFee: z.number().nonnegative(),
1533
1609
  providerPassthrough: z.number().nonnegative(),
1610
+ // The flat own-key fee per minute (burn-down B-26); 0 when every provider runs on the platform's keys. Optional
1611
+ // only for responses from an API older than the fee.
1612
+ ownKeyFee: z.number().nonnegative().optional(),
1534
1613
  telephony: z.number().nonnegative(),
1535
1614
  allIn: z.number().nonnegative()
1536
1615
  });
@@ -1760,9 +1839,46 @@ var init_call_review = __esm({
1760
1839
  });
1761
1840
  }
1762
1841
  });
1842
+ var CAPTURED_FIELDS_MAX, CAPTURED_STRING_MAX_CHARS, CAPTURED_ARRAY_MAX_ITEMS, CapturedFieldValue, CapturedField, StructuredOutputs;
1843
+ var init_call_outcome = __esm({
1844
+ "../contracts/src/call-outcome.ts"() {
1845
+ init_agent_config();
1846
+ CAPTURED_FIELDS_MAX = 200;
1847
+ CAPTURED_STRING_MAX_CHARS = 8e3;
1848
+ CAPTURED_ARRAY_MAX_ITEMS = 100;
1849
+ CapturedFieldValue = z.union([
1850
+ z.string().max(CAPTURED_STRING_MAX_CHARS),
1851
+ z.number().finite(),
1852
+ z.boolean(),
1853
+ z.array(z.string().max(CAPTURED_STRING_MAX_CHARS)).max(CAPTURED_ARRAY_MAX_ITEMS),
1854
+ z.null()
1855
+ ]);
1856
+ CapturedField = z.object({
1857
+ name: z.string().min(1).max(64),
1858
+ type: ProcessFieldType,
1859
+ required: z.boolean(),
1860
+ // false ⇒ the call never captured it; `value` is then null
1861
+ filled: z.boolean(),
1862
+ value: CapturedFieldValue
1863
+ });
1864
+ StructuredOutputs = z.record(CapturedFieldValue);
1865
+ z.object({
1866
+ // Every declared field, in declaration order; empty when the agent declares none.
1867
+ fields: z.array(CapturedField).max(CAPTURED_FIELDS_MAX)
1868
+ }).superRefine((body, ctx) => {
1869
+ const seen = /* @__PURE__ */ new Set();
1870
+ for (const f of body.fields) {
1871
+ if (seen.has(f.name)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: `duplicate field ${f.name}` });
1872
+ seen.add(f.name);
1873
+ if (!f.filled && f.value !== null) ctx.addIssue({ code: z.ZodIssueCode.custom, message: `${f.name}: an unfilled field has no value` });
1874
+ }
1875
+ });
1876
+ }
1877
+ });
1763
1878
  var TranscriptTurn, ConsultationAudit, ToolCallAudit, McpInteractionAudit;
1764
1879
  var init_call_result = __esm({
1765
1880
  "../contracts/src/call-result.ts"() {
1881
+ init_call_outcome();
1766
1882
  TranscriptTurn = z.object({
1767
1883
  speaker: z.enum(["caller", "agent"]),
1768
1884
  text: z.string(),
@@ -1811,9 +1927,13 @@ var init_call_result = __esm({
1811
1927
  startedAt: z.string().datetime(),
1812
1928
  endedAt: z.string().datetime(),
1813
1929
  durationMs: z.number().int().min(0),
1930
+ // Final segments only, in call order.
1814
1931
  transcript: z.array(TranscriptTurn),
1815
- // Whatever the agent's process-schema captured. No prescribed shape.
1816
- structuredOutputs: z.record(z.unknown()).optional(),
1932
+ // The filled fields of the agent's process / flow, keyed by name (burn-down G-20). Absent when the agent declares no
1933
+ // fields, or its worker never reported them.
1934
+ structuredOutputs: StructuredOutputs.optional(),
1935
+ // Every declared field with what the call captured and whether it was filled — the same list `call.ended` carries.
1936
+ fields: z.array(CapturedField).optional(),
1817
1937
  recordingUrl: z.string().url().optional(),
1818
1938
  cost: z.object({
1819
1939
  totalUsd: z.string(),
@@ -1825,9 +1945,11 @@ var init_call_result = __esm({
1825
1945
  });
1826
1946
  }
1827
1947
  });
1828
- var WEBHOOK_EVENT_TYPES, CallWebhookDirection, CallWebhookOutcome, CallWebhookAttributes, Party;
1948
+ var WEBHOOK_EVENT_TYPES, CallWebhookDirection, CallWebhookOutcome, CallWebhookAttributes, Party, CALL_ENDED_PAYLOAD_VERSION, CallEndedTranscript;
1829
1949
  var init_webhook_events = __esm({
1830
1950
  "../contracts/src/webhook-events.ts"() {
1951
+ init_call_result();
1952
+ init_call_outcome();
1831
1953
  WEBHOOK_EVENT_TYPES = ["call.started", "call.ended", "recording.ready"];
1832
1954
  z.enum(WEBHOOK_EVENT_TYPES);
1833
1955
  CallWebhookDirection = z.enum(["inbound", "outbound", "web"]);
@@ -1845,7 +1967,18 @@ var init_webhook_events = __esm({
1845
1967
  // set, it is the same value `call.ended` carries; `call.ended` always has one.
1846
1968
  direction: CallWebhookDirection.nullable()
1847
1969
  });
1970
+ CALL_ENDED_PAYLOAD_VERSION = 2;
1971
+ CallEndedTranscript = z.object({
1972
+ // Final segments only (what was said, never an interim STT guess), in call order. Empty when `truncated`.
1973
+ turns: z.array(TranscriptTurn),
1974
+ // How many final turns the call has — the length of `turns` unless truncated.
1975
+ turnCount: z.number().int().min(0),
1976
+ truncated: z.boolean(),
1977
+ // Where the whole transcript is: GET /v1/calls/:callId/result (its `transcript`). Always set.
1978
+ fetchPath: z.string().regex(/^\/v1\/calls\/[0-9a-f-]{36}\/result$/)
1979
+ });
1848
1980
  z.object({
1981
+ version: z.literal(CALL_ENDED_PAYLOAD_VERSION),
1849
1982
  callId: z.string().uuid(),
1850
1983
  durationMs: z.number().int().min(0),
1851
1984
  endedAt: z.string().datetime(),
@@ -1856,7 +1989,20 @@ var init_webhook_events = __esm({
1856
1989
  toE164: Party,
1857
1990
  direction: CallWebhookDirection,
1858
1991
  outcome: CallWebhookOutcome,
1859
- attributes: CallWebhookAttributes
1992
+ attributes: CallWebhookAttributes,
1993
+ // Every field the agent declared (its process, or a flow's slots), in declaration order, with what the call captured
1994
+ // and whether it was filled. Empty when the agent declares none.
1995
+ fields: z.array(CapturedField),
1996
+ // The filled fields as one object keyed by name (as GET /v1/calls/:id/result returns them). Absent when the agent
1997
+ // declares no fields.
1998
+ structuredOutputs: StructuredOutputs.optional(),
1999
+ transcript: CallEndedTranscript,
2000
+ // true: the agent's worker confirmed its last transcript segment and captured field had landed before this payload
2001
+ // was built. false: built without that word (a worker that crashed or runs an SDK before 0.6.2, or one that didn't
2002
+ // answer within the hold) — the transcript and fields are what had arrived; GET /v1/calls/:id/result has any later.
2003
+ finalized: z.boolean(),
2004
+ // true: the payload would have passed CALL_ENDED_PAYLOAD_MAX_BYTES, so values were left out (see the cap) — fetch them.
2005
+ valuesOmitted: z.boolean()
1860
2006
  });
1861
2007
  z.object({
1862
2008
  callId: z.string().uuid(),
@@ -2432,166 +2578,6 @@ var init_telephony = __esm({
2432
2578
  });
2433
2579
  }
2434
2580
  });
2435
- var SHORTENER_HOSTS, PLACEHOLDER_HOSTS, PublicUrl, A2pBusinessType, A2pBusinessInfo, A2pOptInType, A2pUseCase, A2pMessagingProfile, A2pState;
2436
- var init_a2p = __esm({
2437
- "../contracts/src/a2p.ts"() {
2438
- SHORTENER_HOSTS = [
2439
- "bit.ly",
2440
- "tinyurl.com",
2441
- "t.co",
2442
- "goo.gl",
2443
- "ow.ly",
2444
- "is.gd",
2445
- "buff.ly",
2446
- "rebrand.ly",
2447
- "cutt.ly",
2448
- "shorturl.at",
2449
- "tiny.cc"
2450
- ];
2451
- PLACEHOLDER_HOSTS = ["acme.com", "example.com", "example.org", "test.com", "localhost"];
2452
- PublicUrl = z.string().url().refine((v) => v.startsWith("https://"), "must be https").refine((v) => {
2453
- try {
2454
- const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
2455
- return !SHORTENER_HOSTS.includes(host);
2456
- } catch {
2457
- return false;
2458
- }
2459
- }, "public URL shorteners are rejected by carriers \u2014 use your own domain").refine((v) => {
2460
- try {
2461
- const host = new URL(v).hostname.toLowerCase().replace(/^www\./, "");
2462
- return !PLACEHOLDER_HOSTS.some((p) => host === p || host.endsWith(`.${p}`));
2463
- } catch {
2464
- return false;
2465
- }
2466
- }, "placeholder domain \u2014 reviewers will follow this link and reject the campaign");
2467
- A2pBusinessType = z.enum([
2468
- "Sole Proprietorship",
2469
- "Partnership",
2470
- "Corporation",
2471
- "Co-operative",
2472
- "Limited Liability Corporation",
2473
- "Non-profit Corporation"
2474
- ]);
2475
- A2pBusinessInfo = z.object({
2476
- legalName: z.string().trim().min(2).max(200),
2477
- /** EIN (US) or equivalent registration number. */
2478
- registrationNumber: z.string().trim().min(4).max(50),
2479
- businessType: A2pBusinessType,
2480
- /** Publicly reachable production site — not staging, not a 404. */
2481
- website: PublicUrl,
2482
- industry: z.string().trim().min(2).max(60),
2483
- address: z.object({
2484
- street: z.string().trim().min(2).max(200),
2485
- city: z.string().trim().min(1).max(100),
2486
- region: z.string().trim().min(1).max(100),
2487
- postalCode: z.string().trim().min(2).max(20),
2488
- isoCountry: z.string().trim().length(2)
2489
- }),
2490
- authorizedRep: z.object({
2491
- firstName: z.string().trim().min(1).max(100),
2492
- lastName: z.string().trim().min(1).max(100),
2493
- email: z.string().trim().email(),
2494
- phone: z.string().trim().regex(/^\+[1-9]\d{6,14}$/, "must be E.164"),
2495
- jobTitle: z.string().trim().min(2).max(100)
2496
- })
2497
- });
2498
- A2pOptInType = z.enum(["WEB_FORM", "PAPER_FORM", "VERBAL", "VIA_TEXT", "MOBILE_QR_CODE"]);
2499
- A2pUseCase = z.enum([
2500
- "MIXED",
2501
- "CUSTOMER_CARE",
2502
- "MARKETING",
2503
- "ACCOUNT_NOTIFICATION",
2504
- "2FA",
2505
- "DELIVERY_NOTIFICATION",
2506
- "HIGHER_EDUCATION",
2507
- "POLLING_VOTING",
2508
- "PUBLIC_SERVICE_ANNOUNCEMENT",
2509
- "LOW_VOLUME"
2510
- ]);
2511
- A2pMessagingProfile = z.object({
2512
- useCase: A2pUseCase,
2513
- /** Specific, not generic. "We send texts" gets rejected; describe the actual
2514
- * messages and when they are sent. */
2515
- description: z.string().trim().min(40).max(4096),
2516
- /**
2517
- * The single most-rejected field. Must describe HOW people opt in, state the
2518
- * message frequency, include the "message and data rates may apply"
2519
- * disclosure, and link to publicly reachable evidence. Twilio's API bounds it
2520
- * to 40–2049 characters.
2521
- */
2522
- messageFlow: z.string().trim().min(40).max(2049),
2523
- optInType: A2pOptInType,
2524
- /** Publicly accessible screenshots/pages showing the opt-in. Reviewers open
2525
- * these; anything behind a login fails. */
2526
- optInEvidenceUrls: z.array(PublicUrl).min(1).max(5),
2527
- /** Real messages the customer will send. Must reflect the declared use case
2528
- * and carry opt-out language. */
2529
- messageSamples: z.array(z.string().trim().min(10).max(1024)).min(2).max(5),
2530
- privacyPolicyUrl: PublicUrl,
2531
- termsAndConditionsUrl: PublicUrl,
2532
- hasEmbeddedLinks: z.boolean().default(false),
2533
- hasEmbeddedPhone: z.boolean().default(false)
2534
- });
2535
- z.object({
2536
- business: A2pBusinessInfo,
2537
- messaging: A2pMessagingProfile,
2538
- /** Register against Twilio's mock endpoints — exercises the full pipeline
2539
- * with no fees and no real carrier submission. Used in CI and staging. */
2540
- mock: z.boolean().default(false)
2541
- }).superRefine((v, ctx) => {
2542
- const flow = v.messaging.messageFlow.toLowerCase();
2543
- if (!/(msg|message)\s*(&|and)\s*data rates/.test(flow)) {
2544
- ctx.addIssue({
2545
- code: z.ZodIssueCode.custom,
2546
- path: ["messaging", "messageFlow"],
2547
- message: 'must include a "Message and data rates may apply" disclosure \u2014 carriers reject without it'
2548
- });
2549
- }
2550
- if (!/\d/.test(flow) || !/(msg|message|text)/.test(flow)) {
2551
- ctx.addIssue({
2552
- code: z.ZodIssueCode.custom,
2553
- path: ["messaging", "messageFlow"],
2554
- message: 'must state message frequency, e.g. "Up to 4 msgs/month"'
2555
- });
2556
- }
2557
- const hasOptOut = v.messaging.messageSamples.some((s) => /stop/i.test(s));
2558
- if (!hasOptOut) {
2559
- ctx.addIssue({
2560
- code: z.ZodIssueCode.custom,
2561
- path: ["messaging", "messageSamples"],
2562
- message: 'at least one sample must include opt-out language (e.g. "Reply STOP to opt out")'
2563
- });
2564
- }
2565
- });
2566
- A2pState = z.enum([
2567
- "none",
2568
- "profile_pending",
2569
- "profile_approved",
2570
- "profile_failed",
2571
- "brand_pending",
2572
- "brand_approved",
2573
- "brand_failed",
2574
- "campaign_pending",
2575
- "messaging_ready",
2576
- "campaign_failed"
2577
- ]);
2578
- z.object({
2579
- state: A2pState,
2580
- customerProfileSid: z.string().nullable(),
2581
- trustProductSid: z.string().nullable(),
2582
- brandSid: z.string().nullable(),
2583
- messagingServiceSid: z.string().nullable(),
2584
- campaignSid: z.string().nullable(),
2585
- mock: z.boolean(),
2586
- /** Carrier/Twilio rejection details, surfaced verbatim so the customer can
2587
- * fix the specific field rather than guess. */
2588
- failures: z.array(z.object({ code: z.number().nullable(), field: z.string().nullable(), message: z.string() })).default([]),
2589
- /** Plain-language next step for the dashboard. */
2590
- nextAction: z.string().nullable(),
2591
- updatedAt: z.string().nullable()
2592
- });
2593
- }
2594
- });
2595
2581
  var ConnectorMode, ConnectorStatus, ConnectorNormalize;
2596
2582
  var init_connector = __esm({
2597
2583
  "../contracts/src/connector.ts"() {
@@ -2706,7 +2692,7 @@ var init_connector_stream = __esm({
2706
2692
  ]);
2707
2693
  }
2708
2694
  });
2709
- var FLOW_BOOT_FAILURES;
2695
+ var FLOW_BOOT_FAILURES, AGENT_END_REASONS;
2710
2696
  var init_call_end_reason = __esm({
2711
2697
  "../contracts/src/call-end-reason.ts"() {
2712
2698
  FLOW_BOOT_FAILURES = [
@@ -2717,9 +2703,11 @@ var init_call_end_reason = __esm({
2717
2703
  "worker_identity_refused",
2718
2704
  "provider_unavailable"
2719
2705
  ];
2720
- z.enum(
2721
- FLOW_BOOT_FAILURES.map((cause) => `flow_boot:${cause}`)
2722
- );
2706
+ AGENT_END_REASONS = [
2707
+ "max_duration",
2708
+ ...FLOW_BOOT_FAILURES.map((cause) => `flow_boot:${cause}`)
2709
+ ];
2710
+ z.enum(AGENT_END_REASONS);
2723
2711
  }
2724
2712
  });
2725
2713
  var EnvironmentSpec;
@@ -3915,6 +3903,23 @@ var init_wallet = __esm({
3915
3903
  });
3916
3904
  }
3917
3905
  });
3906
+ var PipelineSlotKey;
3907
+ var init_pipeline_keys = __esm({
3908
+ "../contracts/src/pipeline-keys.ts"() {
3909
+ PipelineSlotKey = z.object({
3910
+ /** The provider as the slot reports it (LiveKit's label: `openai`, `api.openai.com`, `Deepgram`, …). */
3911
+ provider: z.string().min(1).max(200),
3912
+ /** true = the workspace's own key built it; false = the platform's. */
3913
+ byok: z.boolean()
3914
+ });
3915
+ z.object({
3916
+ llm: PipelineSlotKey.optional(),
3917
+ stt: PipelineSlotKey.optional(),
3918
+ tts: PipelineSlotKey.optional(),
3919
+ realtime: PipelineSlotKey.optional()
3920
+ });
3921
+ }
3922
+ });
3918
3923
 
3919
3924
  // ../contracts/src/index.ts
3920
3925
  var init_src = __esm({
@@ -3944,6 +3949,7 @@ var init_src = __esm({
3944
3949
  init_tests();
3945
3950
  init_call_review();
3946
3951
  init_call_result();
3952
+ init_call_outcome();
3947
3953
  init_webhook_events();
3948
3954
  init_numbers();
3949
3955
  init_project_limits();
@@ -3959,12 +3965,12 @@ var init_src = __esm({
3959
3965
  init_connection();
3960
3966
  init_secret_headers();
3961
3967
  init_telephony();
3962
- init_a2p();
3963
3968
  init_connector();
3964
3969
  init_connector_stream();
3965
3970
  init_flow_compile();
3966
3971
  init_flow_compile_check();
3967
3972
  init_call_end_reason();
3973
+ init_call_limits();
3968
3974
  init_flow_enrichment();
3969
3975
  init_environments();
3970
3976
  init_variables();
@@ -3980,6 +3986,7 @@ var init_src = __esm({
3980
3986
  init_wallet();
3981
3987
  init_network_address();
3982
3988
  init_dispatch_metadata();
3989
+ init_pipeline_keys();
3983
3990
  }
3984
3991
  });
3985
3992
 
@@ -4159,9 +4166,78 @@ var init_dist = __esm({
4159
4166
  init_chat_params();
4160
4167
  }
4161
4168
  });
4169
+ function requestedModel(body) {
4170
+ const model2 = body?.model;
4171
+ return typeof model2 === "string" && model2 ? model2 : void 0;
4172
+ }
4173
+ function safely(fn) {
4174
+ try {
4175
+ fn();
4176
+ } catch (err) {
4177
+ console.warn("[voicelayer] model meter listener failed", { err: err instanceof Error ? err.message : String(err) });
4178
+ }
4179
+ }
4180
+ function metered(target, create, byok) {
4181
+ return async (...args) => {
4182
+ const listener = meterScope.getStore();
4183
+ const startedAt = Date.now();
4184
+ const model2 = requestedModel(args[0]);
4185
+ if (listener) safely(() => listener.started());
4186
+ let res;
4187
+ try {
4188
+ res = await Reflect.apply(create, target, args);
4189
+ return res;
4190
+ } finally {
4191
+ if (listener) {
4192
+ const usage = res?.usage;
4193
+ const record = {
4194
+ provider: "openai",
4195
+ model: model2 ?? res?.model ?? "unknown",
4196
+ ...usage ? { usage: { inputTokens: usage.prompt_tokens ?? 0, outputTokens: usage.completion_tokens ?? 0 } } : {},
4197
+ ...byok !== void 0 ? { byok } : {},
4198
+ startedAt,
4199
+ endedAt: Date.now()
4200
+ };
4201
+ safely(() => listener.finished(record));
4202
+ }
4203
+ }
4204
+ };
4205
+ }
4206
+ function meteredClient(client, byok) {
4207
+ const hit = wrapped.get(client);
4208
+ if (hit) return hit;
4209
+ const chat = client.chat;
4210
+ const embeddings = client.embeddings;
4211
+ const completionsCreate = chat ? metered(chat.completions, chat.completions.create, byok) : void 0;
4212
+ const embeddingsCreate = embeddings ? metered(embeddings, embeddings.create, byok) : void 0;
4213
+ const completions = chat ? new Proxy(chat.completions, { get: (t, p, r) => p === "create" ? completionsCreate : Reflect.get(t, p, r) }) : void 0;
4214
+ const chatProxy = chat ? new Proxy(chat, { get: (t, p, r) => p === "completions" ? completions : Reflect.get(t, p, r) }) : void 0;
4215
+ const embeddingsProxy = embeddings ? new Proxy(embeddings, { get: (t, p, r) => p === "create" ? embeddingsCreate : Reflect.get(t, p, r) }) : void 0;
4216
+ const proxy = new Proxy(client, {
4217
+ get(target, prop, receiver) {
4218
+ if (prop === "chat" && chatProxy) return chatProxy;
4219
+ if (prop === "embeddings" && embeddingsProxy) return embeddingsProxy;
4220
+ return Reflect.get(target, prop, receiver);
4221
+ }
4222
+ });
4223
+ wrapped.set(client, proxy);
4224
+ wrapped.set(proxy, proxy);
4225
+ return proxy;
4226
+ }
4227
+ var meterScope, wrapped;
4228
+ var init_model_meter = __esm({
4229
+ "src/runtime/model-meter.ts"() {
4230
+ meterScope = new AsyncLocalStorage();
4231
+ wrapped = /* @__PURE__ */ new WeakMap();
4232
+ }
4233
+ });
4162
4234
  function runWithOpenAIScope(client, fn) {
4163
4235
  return scope.run(client, fn);
4164
4236
  }
4237
+ function helperKeyIsCustomers() {
4238
+ if (credential) return true;
4239
+ return callCredentialSource !== "worker-token";
4240
+ }
4165
4241
  function defaultOpenAIOptions() {
4166
4242
  if (!credential) return {};
4167
4243
  return {
@@ -4169,27 +4245,31 @@ function defaultOpenAIOptions() {
4169
4245
  ...credential.baseURL ? { baseURL: credential.baseURL } : {}
4170
4246
  };
4171
4247
  }
4172
- function internalChatClient(opts) {
4248
+ function internalChatClient() {
4173
4249
  const cred = defaultOpenAIOptions();
4174
4250
  return createChatClient({
4175
4251
  ...cred.apiKey ? { apiKey: cred.apiKey } : {},
4176
- ...cred.baseURL ? { baseURL: cred.baseURL } : {},
4177
- ...{}
4252
+ ...cred.baseURL ? { baseURL: cred.baseURL } : {}
4178
4253
  });
4179
4254
  }
4180
4255
  function defaultOpenAI() {
4181
- return scope.getStore() ?? (cached ??= internalChatClient());
4256
+ const scoped = scope.getStore();
4257
+ if (scoped) return meteredClient(scoped);
4258
+ if (!cached) cached = meteredClient(internalChatClient(), helperKeyIsCustomers());
4259
+ return cached;
4182
4260
  }
4183
4261
  function hasOpenAiKey() {
4184
4262
  return scope.getStore() !== void 0 || credential !== null || Boolean(process.env["OPENAI_API_KEY"]);
4185
4263
  }
4186
- var credential, cached, scope;
4264
+ var credential, cached, scope, callCredentialSource;
4187
4265
  var init_openai_default = __esm({
4188
4266
  "src/runtime/openai-default.ts"() {
4189
4267
  init_dist();
4268
+ init_model_meter();
4190
4269
  credential = null;
4191
4270
  cached = null;
4192
4271
  scope = new AsyncLocalStorage();
4272
+ callCredentialSource = null;
4193
4273
  }
4194
4274
  });
4195
4275
  var init_ssrf = __esm({