@spekoai/sdk 0.5.1 → 0.5.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +88 -0
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/lib/client.d.ts +13 -1
  7. package/dist/lib/client.d.ts.map +1 -1
  8. package/dist/lib/client.js +22 -2
  9. package/dist/lib/http.d.ts +11 -3
  10. package/dist/lib/http.d.ts.map +1 -1
  11. package/dist/lib/http.js +29 -12
  12. package/dist/lib/resources/agents.js +1 -0
  13. package/dist/lib/resources/calls.d.ts +19 -1
  14. package/dist/lib/resources/calls.d.ts.map +1 -1
  15. package/dist/lib/resources/calls.js +22 -0
  16. package/dist/lib/resources/phone-numbers.d.ts +2 -1
  17. package/dist/lib/resources/phone-numbers.d.ts.map +1 -1
  18. package/dist/lib/resources/phone-numbers.js +2 -1
  19. package/dist/lib/resources/realtime.d.ts +3 -5
  20. package/dist/lib/resources/realtime.d.ts.map +1 -1
  21. package/dist/lib/resources/realtime.js +829 -91
  22. package/dist/lib/resources/sessions.d.ts +53 -0
  23. package/dist/lib/resources/sessions.d.ts.map +1 -0
  24. package/dist/lib/resources/sessions.js +166 -0
  25. package/dist/lib/resources/sms.d.ts +80 -0
  26. package/dist/lib/resources/sms.d.ts.map +1 -0
  27. package/dist/lib/resources/sms.js +152 -0
  28. package/dist/lib/resources/transcribe.d.ts.map +1 -1
  29. package/dist/lib/resources/transcribe.js +5 -2
  30. package/dist/lib/resources/voice.d.ts +283 -1
  31. package/dist/lib/resources/voice.d.ts.map +1 -1
  32. package/dist/lib/resources/voice.js +345 -0
  33. package/dist/lib/resources/webhooks.d.ts +25 -0
  34. package/dist/lib/resources/webhooks.d.ts.map +1 -0
  35. package/dist/lib/resources/webhooks.js +46 -0
  36. package/dist/lib/types/index.d.ts +944 -9
  37. package/dist/lib/types/index.d.ts.map +1 -1
  38. package/dist/lib/voice-contract.d.ts +280 -0
  39. package/dist/lib/voice-contract.d.ts.map +1 -0
  40. package/dist/lib/voice-contract.js +115 -0
  41. package/package.json +2 -1
  42. package/src/index.ts +212 -0
  43. package/src/lib/client.ts +169 -0
  44. package/src/lib/errors.ts +28 -0
  45. package/src/lib/http.ts +442 -0
  46. package/src/lib/resources/agents.ts +211 -0
  47. package/src/lib/resources/callbacks.ts +40 -0
  48. package/src/lib/resources/calls.ts +113 -0
  49. package/src/lib/resources/complete.ts +63 -0
  50. package/src/lib/resources/credits.ts +41 -0
  51. package/src/lib/resources/knowledge-bases.ts +199 -0
  52. package/src/lib/resources/phone-numbers.ts +109 -0
  53. package/src/lib/resources/realtime-globals.d.ts +31 -0
  54. package/src/lib/resources/realtime.spec.ts +565 -0
  55. package/src/lib/resources/realtime.ts +1169 -0
  56. package/src/lib/resources/sessions.ts +191 -0
  57. package/src/lib/resources/sms.ts +214 -0
  58. package/src/lib/resources/synthesize.ts +101 -0
  59. package/src/lib/resources/transcribe.ts +91 -0
  60. package/src/lib/resources/usage.ts +24 -0
  61. package/src/lib/resources/voice.ts +426 -0
  62. package/src/lib/resources/voices.ts +32 -0
  63. package/src/lib/resources/webhooks.ts +67 -0
  64. package/src/lib/types/index.ts +2409 -0
  65. package/src/lib/voice-contract.ts +358 -0
@@ -1,7 +1,10 @@
1
+ import type { CallDirection, CallJoinCredentials, CallResource, CallStatus } from '../voice-contract.js';
1
2
  /** Options for creating a Speko client. */
2
3
  export interface SpekoClientOptions {
3
4
  /** API key for authentication. */
4
5
  apiKey: string;
6
+ /** Org-defined broker identity used by human-calling methods. */
7
+ brokerId?: string;
5
8
  /** Base URL of the Speko API. Defaults to https://api.speko.dev */
6
9
  baseUrl?: string;
7
10
  /** Alias for {@link SpekoClientOptions.baseUrl}. If both are set, `baseUrl` wins. */
@@ -105,6 +108,10 @@ export interface TranscribeOptions extends RoutingIntent {
105
108
  * ElevenLabs Scribe → `biased_keywords`. Casing matters for proper nouns.
106
109
  */
107
110
  keywords?: readonly string[];
111
+ /** Provider-facing STT overrides. Routing continues to use the inherited language. */
112
+ sttOptions?: {
113
+ language?: string;
114
+ };
108
115
  }
109
116
  export interface TranscribeResult {
110
117
  text: string;
@@ -250,6 +257,13 @@ export interface ChatMessage {
250
257
  * org-installed Speko app action such as Google Calendar or Slack.
251
258
  */
252
259
  export type ChatToolExecutionMode = 'inline' | 'webhook' | 'builtin' | 'integration';
260
+ /**
261
+ * Spoken lead-in behavior before a server-executed tool runs. `auto` lets the
262
+ * gateway decide from the tool's recent execution durations; `always` forces a
263
+ * spoken lead-in (the gateway injects one when the model didn't produce any);
264
+ * `never` runs the tool silently.
265
+ */
266
+ export type ChatToolPreToolSpeech = 'auto' | 'always' | 'never';
253
267
  /**
254
268
  * Source-of-execution config. Required when `executionMode` is
255
269
  * `webhook`, `builtin`, or `integration`. Mirrors the SpekoTool `source` shape inside
@@ -263,6 +277,15 @@ export type ChatToolSource = {
263
277
  /** Pointer into Speko's secrets store. Created via `POST /v1/agents/:id/tools` (which encrypts and stores the raw secret). */
264
278
  secretRef: string;
265
279
  headers?: Record<string, string>;
280
+ /**
281
+ * Outbound auth headers whose values are secret-referenced (resolved and
282
+ * injected by Speko at call time). The raw credential never leaves the
283
+ * server — only the `secretRef` pointer is exposed.
284
+ */
285
+ authHeaders?: Array<{
286
+ name: string;
287
+ secretRef: string;
288
+ }>;
266
289
  timeoutMs?: number;
267
290
  /** `async` returns `asyncAck` immediately while Speko dispatches the webhook in the background. */
268
291
  responseMode?: 'sync' | 'async';
@@ -295,6 +318,8 @@ export interface ChatTool {
295
318
  parameters: Record<string, unknown>;
296
319
  executionMode?: ChatToolExecutionMode;
297
320
  source?: ChatToolSource;
321
+ /** Spoken lead-in behavior before this tool executes. Defaults to `auto` for registered tools. */
322
+ preToolSpeech?: ChatToolPreToolSpeech;
298
323
  }
299
324
  /** Mirrors LiveKit's `ToolChoice` for parity with the agents framework. */
300
325
  export type ChatToolChoice = 'auto' | 'none' | 'required' | {
@@ -368,13 +393,15 @@ export type CompleteStreamEvent = {
368
393
  error: string;
369
394
  code: string;
370
395
  };
371
- export type RealtimeProvider = 'openai' | 'google' | 'xai' | 'inworld' | 'alibaba';
396
+ export type RealtimeProvider = 'openai' | 'google' | 'xai';
372
397
  export interface RealtimeToolSpec {
373
398
  name: string;
374
399
  description: string;
375
400
  parameters: Record<string, unknown>;
376
401
  }
377
402
  export interface RealtimeConnectParams {
403
+ /** Persisted agent whose workspace webhook routes should receive lifecycle events. */
404
+ agentId?: string;
378
405
  provider: RealtimeProvider;
379
406
  model: string;
380
407
  voice?: string;
@@ -383,9 +410,13 @@ export interface RealtimeConnectParams {
383
410
  inputSampleRate?: 16000 | 24000;
384
411
  outputSampleRate?: 16000 | 24000;
385
412
  tools?: RealtimeToolSpec[];
413
+ /** Exact-match attributes used only for workspace webhook routing. Requires agentId. */
414
+ webhookTags?: Record<string, string>;
386
415
  metadata?: Record<string, unknown>;
387
416
  /** Max session duration in seconds. Server-capped at 1800 (30 min). */
388
417
  ttlSeconds?: number;
418
+ /** Reuse when retrying an ambiguous bootstrap timeout. Generated when omitted. */
419
+ idempotencyKey?: string;
389
420
  }
390
421
  /**
391
422
  * Event shape emitted by a `RealtimeSessionHandle`. Binary audio comes in
@@ -465,6 +496,52 @@ export interface VoiceDialParams {
465
496
  systemPrompt?: string;
466
497
  /** Optional first utterance. `null` is not accepted by phone dial; omit to use the agent default. */
467
498
  firstMessage?: string;
499
+ /**
500
+ * Call-time values for template variables in `systemPrompt` / `firstMessage`.
501
+ * Sending this key (even `{}`) — or dialing with an agent that declares
502
+ * variables in its registry — compiles both strings as Liquid templates at
503
+ * call-create time: `{{name}}` interpolation, `{% if %}` / `{% elsif %}`
504
+ * branching, `| default:` filters, and platform-provided `system.*` values
505
+ * (`system.now`, `system.caller_number`, `system.call_id`, …).
506
+ *
507
+ * Resolution per variable: this map → the agent registry's default → inline
508
+ * `| default:` → the request fails with 400 `MISSING_TEMPLATE_VARIABLES`
509
+ * listing every unresolved name. Keys under `system.` are rejected. Omit
510
+ * this field entirely to send both strings verbatim (no compilation).
511
+ *
512
+ * @example
513
+ * ```ts
514
+ * await speko.voice.dial({
515
+ * to: '+12015551234',
516
+ * agentId: 'ag_123',
517
+ * systemPrompt:
518
+ * 'You are {{agent_name | default: "Ava"}} calling {{customer}}. ' +
519
+ * '{% if plan == "premium" %}Offer the priority upgrade.{% endif %} ' +
520
+ * 'The current time is {{system.now}}.',
521
+ * variables: { customer: 'Mr. Lee', plan: 'premium' },
522
+ * });
523
+ * ```
524
+ */
525
+ variables?: Record<string, string>;
526
+ /**
527
+ * Per-call values for TOOLS ONLY — e.g. a short-lived access token scoped to
528
+ * the person being called, or a per-tenant API base URL. Unlike `variables`
529
+ * these never enter the system prompt, the transcript, or the model's
530
+ * context; they are stored encrypted and released only to tool execution.
531
+ * Custom-code tools read `session.secrets.<name>`; webhook tools may
532
+ * reference `{{name}}` in their `url` and `headers`. Names must be
533
+ * identifiers (`[A-Za-z_][A-Za-z0-9_]*`); up to 32 entries, 6 chars–4 KB each.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * await speko.voice.dial({
538
+ * to: '+12015551234',
539
+ * agentId: 'ag_123',
540
+ * toolSecrets: { base_url: 'https://acme.example.com', access_token: token },
541
+ * });
542
+ * ```
543
+ */
544
+ toolSecrets?: Record<string, string>;
468
545
  llm?: {
469
546
  temperature?: number;
470
547
  maxTokens?: number;
@@ -475,6 +552,60 @@ export interface VoiceDialParams {
475
552
  };
476
553
  sttOptions?: {
477
554
  keywords?: string[];
555
+ prompt?: string;
556
+ language?: string;
557
+ };
558
+ /** Server-side wall-clock cap in seconds. Values are clamped server-side to 30s-4h. */
559
+ maxDurationSeconds?: number;
560
+ /**
561
+ * Optional per-call turn-taking overrides. `greetFirst` defaults ON for
562
+ * outbound (worker-side, 2026-07-03): the greeting plays immediately while
563
+ * AMD classifies in the background. Pass false to hold the greeting for the
564
+ * AMD verdict.
565
+ */
566
+ turnHandling?: {
567
+ /** Local VAD for cascaded calls. Omit to use Silero. */
568
+ vad?: {
569
+ provider: 'silero' | 'ai-coustics';
570
+ };
571
+ /**
572
+ * Caller-leg input enhancement (ai-coustics). Omit for the platform default;
573
+ * `enabled: false` turns it off; `model` is plain Quail (default) or Quail Voice
574
+ * Focus (primary-speaker isolation, explicit opt-in).
575
+ */
576
+ noiseCancellation?: {
577
+ enabled: boolean;
578
+ model?: 'quail' | 'quail-voice-focus';
579
+ };
580
+ profile?: 'conversational' | 'ivr' | 'ivr_patient';
581
+ endpointing?: {
582
+ minDelay?: number;
583
+ maxDelay?: number;
584
+ };
585
+ interruption?: {
586
+ mode?: 'adaptive' | 'vad';
587
+ minDuration?: number;
588
+ minWords?: number;
589
+ };
590
+ turnDetection?: boolean | 'stt';
591
+ contextThreshold?: boolean;
592
+ greetFirst?: boolean;
593
+ /**
594
+ * Replaces the built-in prompt the answering-machine detector's classifier
595
+ * sees when deciding whether a human, an IVR menu or voicemail answered.
596
+ * Outbound only; max 2,000 characters.
597
+ */
598
+ amdPrompt?: string;
599
+ /**
600
+ * What happens when native detection identifies recordable voicemail. `hangup` ends the call at the verdict; `leave_message` waits
601
+ * for the greeting to finish, speaks `voicemailMessage` once, then hangs
602
+ * up; `agent_decides` (default) hands the verdict to the LLM and lets the
603
+ * prompt's own voicemail rules act. Unavailable mailboxes always end without
604
+ * a message. Does not apply to menus/screeners or enable disabled/carrier AMD.
605
+ */
606
+ onMachine?: 'hangup' | 'leave_message' | 'agent_decides';
607
+ /** Spoken once into the mailbox under `onMachine: 'leave_message'`. Renders the same `{{variables}}` as `firstMessage`. Max 2,000 characters. */
608
+ voicemailMessage?: string;
478
609
  };
479
610
  /** Optional per-call SIP routing hints. Carrier AMD requires trunk/provider support. */
480
611
  telephony?: {
@@ -484,8 +615,17 @@ export interface VoiceDialParams {
484
615
  timeoutSeconds?: number;
485
616
  };
486
617
  };
618
+ /** Exact-match attributes used only for workspace webhook routing. Requires agentId. */
619
+ webhookTags?: Record<string, string>;
487
620
  /** Free-form metadata round-tripped to your webhooks. */
488
621
  metadata?: Record<string, unknown>;
622
+ /**
623
+ * @deprecated The agent-initiated end_call tool is now always on; the server
624
+ * accepts this field for compat but ignores it.
625
+ */
626
+ endCall?: {
627
+ enabled: boolean;
628
+ };
489
629
  }
490
630
  export interface VoiceDialResult {
491
631
  sessionId: string;
@@ -496,6 +636,64 @@ export interface VoiceDialResult {
496
636
  to: string;
497
637
  from: string;
498
638
  }
639
+ /**
640
+ * One turn from `GET /v1/sessions/:id/transcript` — the lightweight live
641
+ * transcript poll. Note the camelCase keys: this endpoint's serialization
642
+ * differs from the snake_case `CallTranscriptEntry` embedded in `CallDetail`.
643
+ */
644
+ export interface SessionTranscriptEntry {
645
+ id: string;
646
+ index: number;
647
+ source: 'user' | 'agent' | 'system';
648
+ text: string;
649
+ startedAt: string;
650
+ endedAt: string | null;
651
+ provider: string | null;
652
+ model: string | null;
653
+ /** Per-stage latency legs (ms) — null on user/system turns. */
654
+ eouMs: number | null;
655
+ llmTtftMs: number | null;
656
+ ttsTtfbMs: number | null;
657
+ latencyStatus: 'partial' | 'complete' | 'interrupted' | 'error' | null;
658
+ conversationalLatencyMs: number | null;
659
+ /** Tool calls the agent made on this turn (empty when none). */
660
+ toolCalls: {
661
+ name: string;
662
+ args: string;
663
+ }[];
664
+ }
665
+ export interface SessionTranscript {
666
+ entries: SessionTranscriptEntry[];
667
+ }
668
+ /**
669
+ * One push from `sessions.stream()` (SSE under the hood, auto-reconnecting).
670
+ * `end` is always the final event; transport-level reconnects and server
671
+ * stream rotations are handled inside the SDK and never surface here.
672
+ */
673
+ export type SessionStreamEvent = {
674
+ type: 'status';
675
+ status: string;
676
+ endedAt: string | null;
677
+ } | {
678
+ type: 'transcript';
679
+ turn: SessionTranscriptEntry;
680
+ } | {
681
+ type: 'event';
682
+ event: CallEvent;
683
+ } | {
684
+ type: 'end';
685
+ reason: 'session_ended';
686
+ };
687
+ export interface SessionStreamOptions {
688
+ /**
689
+ * Resume position (`"<lastTurnIndex>:<lastEventCreatedAtMs>"`). Rarely
690
+ * needed — the iterator tracks it internally across reconnects; pass it
691
+ * only to resume a NEW iterator after your own process restarted.
692
+ */
693
+ cursor?: string;
694
+ /** Abort to stop streaming (the iterator returns). */
695
+ signal?: AbortSignal;
696
+ }
499
697
  export type PhoneNumberDirection = 'inbound' | 'outbound' | 'both';
500
698
  export type PhoneNumberSource = 'managed' | 'sip_trunk';
501
699
  export type PhoneNumberSmsAssignmentStatus = 'FAILED_ASSIGNMENT' | 'PENDING_ASSIGNMENT' | 'ASSIGNED' | 'PENDING_UNASSIGNMENT' | 'FAILED_UNASSIGNMENT';
@@ -528,16 +726,34 @@ export interface PhoneNumberRow {
528
726
  smsCampaignId: string | null;
529
727
  smsAssignmentStatus: PhoneNumberSmsAssignmentStatus | null;
530
728
  smsAssignmentUpdatedAt: string | null;
729
+ telnyxMessagingProfileId: string | null;
730
+ smsMessagingProfileStatus: 'pending' | 'ready' | 'failed';
731
+ smsMessagingProfileUpdatedAt: string | null;
732
+ smsMessagingProfileError: string | null;
733
+ smsAutomationEnabled: boolean;
531
734
  /**
532
735
  * 1:1 link to a persisted agent. When set, inbound calls hydrate
533
736
  * pipeline config from the agent row instead of (or alongside) the
534
737
  * dispatch_metadata_template.
535
738
  */
536
739
  agentId: string | null;
740
+ /**
741
+ * Inbound destination when this number answers to a HUMAN rather than an
742
+ * agent — the org-defined broker whose softphone is rung. Mutually
743
+ * exclusive with `agentId`: assigning one clears the other, because a number
744
+ * routed to a broker is provisioned so that no agent joins ahead of them.
745
+ */
746
+ routeToBrokerId: string | null;
537
747
  setupStatus: PhoneNumberSetupStatus;
538
748
  nextChargeAt: string;
539
749
  lastChargedAt: string | null;
750
+ /** Effective billing-or-compliance suspension timestamp. */
540
751
  suspendedAt: string | null;
752
+ /** Billing-only suspension, retained independently from compliance review. */
753
+ billingSuspendedAt?: string | null;
754
+ /** Compliance-only suspension for Speko-managed numbers. */
755
+ complianceSuspendedAt?: string | null;
756
+ suspensionReason?: 'billing' | 'compliance' | null;
541
757
  createdAt: string;
542
758
  updatedAt: string;
543
759
  }
@@ -576,6 +792,15 @@ export interface PhoneNumberUpdateParams {
576
792
  label?: string | null;
577
793
  /** Pass `null` to unlink, a string to relink. */
578
794
  agentId?: string | null;
795
+ /**
796
+ * Route inbound calls on this number to a human broker's softphone instead of
797
+ * an agent — pass your org-defined broker id, or `null` to stop. Setting
798
+ * it clears `agentId`, and setting `agentId` clears it; sending both in one
799
+ * request is a validation error. Requires the human-calling feature.
800
+ */
801
+ routeToBrokerId?: string | null;
802
+ /** Owner/admin-only opt-in for inbound SMS agent replies on this number. */
803
+ smsAutomationEnabled?: boolean;
579
804
  }
580
805
  export interface AvailablePhoneNumber {
581
806
  e164: string;
@@ -623,24 +848,56 @@ export interface PhoneNumberKybAuthorizedRepresentative {
623
848
  email: string;
624
849
  phone?: string;
625
850
  }
851
+ export interface PhoneNumberKybDeclaration {
852
+ businessName: string;
853
+ useCase: string;
854
+ }
855
+ export type PhoneNumberKybAttestor = {
856
+ kind: 'user';
857
+ userId: string;
858
+ name: string;
859
+ email: string;
860
+ organizationRole: string | null;
861
+ } | {
862
+ kind: 'api_key';
863
+ apiKeyId: string;
864
+ };
865
+ export interface PhoneNumberKybAttestationContract {
866
+ version: string;
867
+ text: string;
868
+ termsVersion: string;
869
+ termsUrl: string;
870
+ }
626
871
  export interface PhoneNumberKybDraftParams {
627
872
  businessProfile: PhoneNumberKybBusinessProfile;
628
873
  authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
629
874
  attestationAccepted?: boolean;
630
875
  }
631
- export interface PhoneNumberKybSubmitParams {
876
+ export type PhoneNumberKybSubmitParams = {
877
+ declaration: PhoneNumberKybDeclaration;
878
+ attestationAccepted: true;
879
+ attestationVersion: string;
880
+ } | {
632
881
  businessProfile: PhoneNumberKybBusinessProfile;
633
882
  authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
634
883
  attestationAccepted: true;
635
- }
884
+ attestationVersion?: string;
885
+ };
636
886
  export interface PhoneNumberKybSubmission {
637
887
  id: string;
638
888
  organizationId: string;
639
889
  status: PhoneNumberKybSubmissionStatus;
640
890
  businessProfile: PhoneNumberKybBusinessProfile | null;
641
891
  authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative | null;
892
+ declaration?: PhoneNumberKybDeclaration | null;
893
+ attestor?: PhoneNumberKybAttestor | null;
642
894
  attestationAccepted: boolean;
895
+ attestationVersion?: string | null;
896
+ attestationText?: string | null;
897
+ termsVersion?: string | null;
643
898
  attestedAt: string | null;
899
+ accessHoldAt?: string | null;
900
+ accessHoldReason?: 'rejected' | 'revoked' | null;
644
901
  submittedByUserId: string | null;
645
902
  submittedByEmail: string | null;
646
903
  submittedByApiKeyId: string | null;
@@ -658,6 +915,10 @@ export interface PhoneNumberKybSubmission {
658
915
  export interface PhoneNumberKybOverview {
659
916
  status: PhoneNumberKybStatus;
660
917
  submission: PhoneNumberKybSubmission | null;
918
+ declarationPrefill?: PhoneNumberKybDeclaration;
919
+ requiredAttestation?: PhoneNumberKybAttestationContract;
920
+ attestationRequired?: boolean;
921
+ complianceAccess?: 'enabled' | 'awaiting_attestation' | 'suspended';
661
922
  prefill: {
662
923
  businessProfile: PhoneNumberKybBusinessProfile;
663
924
  authorizedRepresentative: PhoneNumberKybAuthorizedRepresentative;
@@ -689,6 +950,19 @@ export interface AgentStackPreferences {
689
950
  export interface AgentSttOptions {
690
951
  /** Vocabulary keywords forwarded to whichever STT provider the router picks. */
691
952
  keywords?: string[];
953
+ /**
954
+ * Free-text transcription context (domain, names, expected phrases), max
955
+ * 2000 chars. Honored only by prompt-capable STT models (OpenAI
956
+ * gpt-4o-transcribe family, AssemblyAI Universal-3 Pro tiers).
957
+ */
958
+ prompt?: string;
959
+ /**
960
+ * STT stream-language override. A BCP-47-ish tag ('en', 'es-MX'), a
961
+ * provider keyword like Deepgram's 'multi', or 'auto' to let the provider
962
+ * detect the spoken language itself (Soniox auto-detects when hints are
963
+ * omitted). Never affects stack routing — that keeps the agent language.
964
+ */
965
+ language?: string;
692
966
  }
693
967
  /**
694
968
  * Built-in ambience clip ids supported by the hosted worker. Custom clip
@@ -696,7 +970,7 @@ export interface AgentSttOptions {
696
970
  * keeps the v1 API simple and lets the worker map straight to the
697
971
  * `BuiltinAudioClip` enum.
698
972
  */
699
- export type AgentAmbientClip = 'office-ambience' | 'keyboard-typing' | 'keyboard-typing2';
973
+ export type AgentAmbientClip = 'office-ambience' | 'city-ambience' | 'forest-ambience' | 'crowded-room' | 'keyboard-typing' | 'keyboard-typing2';
700
974
  /**
701
975
  * Per-agent background audio. Today only ambient (continuous loop) is
702
976
  * supported. The ambience plays on a separate media track mixed
@@ -706,7 +980,14 @@ export type AgentAmbientClip = 'office-ambience' | 'keyboard-typing' | 'keyboard
706
980
  export interface AgentBackgroundAudio {
707
981
  ambient?: {
708
982
  clip: AgentAmbientClip;
709
- /** Linear gain in [0, 1]. Defaults to 1.0 (clip's natural level). */
983
+ /**
984
+ * Linear gain in `[0, 16]`, defaulting to 1.0 — the clip's own recorded
985
+ * level, which is not the same as "full volume". The built-in clips are
986
+ * mastered roughly 30 dB apart, so the useful range differs per clip:
987
+ * `office-ambience` is very quiet (about -52 LUFS) and needs ~5-10 to sit
988
+ * audibly under speech, `city-ambience` is about right at 1, and
989
+ * `crowded-room` is loud enough that it distorts past ~1.6.
990
+ */
710
991
  volume?: number;
711
992
  };
712
993
  }
@@ -728,7 +1009,10 @@ export interface AgentExtractionField {
728
1009
  */
729
1010
  name: string;
730
1011
  type: 'string' | 'number' | 'boolean' | 'enum';
731
- /** Instruction the LLM uses to extract this field. 1–500 characters. */
1012
+ /**
1013
+ * Instruction the LLM uses to extract this field. 1 to 10,000 characters,
1014
+ * and at most 40,000 characters combined across all fields on the webhook.
1015
+ */
732
1016
  description: string;
733
1017
  /**
734
1018
  * Allowed values — required (and only valid) when `type` is `'enum'`.
@@ -736,11 +1020,30 @@ export interface AgentExtractionField {
736
1020
  */
737
1021
  options?: string[];
738
1022
  }
1023
+ /**
1024
+ * Outbound auth header input — `value` is the plaintext credential Speko
1025
+ * encrypts at rest. Required on create; omit on update to keep the value
1026
+ * already stored under this header's ref.
1027
+ */
1028
+ export interface AgentWebhookAuthHeaderInput {
1029
+ name: string;
1030
+ value?: string;
1031
+ }
1032
+ /** Outbound auth header as returned by the API — value stays server-side. */
1033
+ export interface AgentWebhookAuthHeader {
1034
+ name: string;
1035
+ secretRef: string;
1036
+ }
739
1037
  export interface AgentLifecycleWebhookCreate {
740
1038
  url: string;
741
- /** Deprecated. Lifecycle webhooks use the org-level signing secret from API keys. */
1039
+ /**
1040
+ * Optional per-webhook signing secret. When supplied, this endpoint signs
1041
+ * with its own secret instead of the shared org-level secret from API keys.
1042
+ */
742
1043
  secret?: string;
743
1044
  headers?: Record<string, string>;
1045
+ /** Secret-referenced outbound auth headers (e.g. a Bearer token your endpoint requires). */
1046
+ authHeaders?: AgentWebhookAuthHeaderInput[];
744
1047
  timeoutMs?: number;
745
1048
  responseMode?: 'sync' | 'async';
746
1049
  asyncAck?: string;
@@ -749,9 +1052,14 @@ export interface AgentLifecycleWebhookCreate {
749
1052
  }
750
1053
  export interface AgentLifecycleWebhookUpdate {
751
1054
  url: string;
752
- /** Deprecated. Lifecycle webhooks use the org-level signing secret from API keys. */
1055
+ /**
1056
+ * Optional per-webhook signing secret. Supply to set/rotate a per-webhook
1057
+ * secret; omit to keep the existing (or shared org-level) secret.
1058
+ */
753
1059
  secret?: string;
754
1060
  headers?: Record<string, string>;
1061
+ /** Secret-referenced outbound auth headers. Replaces the stored set; omit a `value` to keep it. */
1062
+ authHeaders?: AgentWebhookAuthHeaderInput[];
755
1063
  timeoutMs?: number;
756
1064
  responseMode?: 'sync' | 'async';
757
1065
  asyncAck?: string;
@@ -762,6 +1070,8 @@ export interface AgentLifecycleWebhookSerialized {
762
1070
  url: string;
763
1071
  secretRef: string;
764
1072
  headers?: Record<string, string>;
1073
+ /** Outbound auth-header pointers; values stay encrypted server-side. */
1074
+ authHeaders?: AgentWebhookAuthHeader[];
765
1075
  timeoutMs?: number;
766
1076
  responseMode?: 'sync' | 'async';
767
1077
  asyncAck?: string;
@@ -772,16 +1082,225 @@ export interface AgentWebhooksSerialized {
772
1082
  preCall?: AgentLifecycleWebhookSerialized;
773
1083
  postCall?: AgentLifecycleWebhookSerialized;
774
1084
  status?: AgentLifecycleWebhookSerialized;
1085
+ /** Dedicated `call.analysis` webhook — LLM analysis results only. */
1086
+ analysis?: AgentLifecycleWebhookSerialized;
1087
+ /** Dedicated `call.recording` webhook — fires when the recording turns terminal. */
1088
+ recording?: AgentLifecycleWebhookSerialized;
775
1089
  }
776
1090
  export interface AgentWebhooksCreate {
777
1091
  preCall?: AgentLifecycleWebhookCreate;
778
1092
  postCall?: AgentLifecycleWebhookCreate;
779
1093
  status?: AgentLifecycleWebhookCreate;
1094
+ /**
1095
+ * Dedicated `call.analysis` webhook. Delivered once per call when the LLM
1096
+ * analysis completes: summary, outcome, structured_data, and custom_data —
1097
+ * without the transcript/cost/recording of the combined `call.report`.
1098
+ */
1099
+ analysis?: AgentLifecycleWebhookCreate;
1100
+ /**
1101
+ * Dedicated `call.recording` webhook. Delivered once per call when the
1102
+ * recording reaches a terminal state — `ready` carries the presigned
1103
+ * `recording_url` (7-day TTL), `failed` carries `recording_url: null`.
1104
+ */
1105
+ recording?: AgentLifecycleWebhookCreate;
780
1106
  }
781
1107
  export interface AgentWebhooksUpdate {
782
1108
  preCall?: AgentLifecycleWebhookUpdate | null;
783
1109
  postCall?: AgentLifecycleWebhookUpdate | null;
784
1110
  status?: AgentLifecycleWebhookUpdate | null;
1111
+ analysis?: AgentLifecycleWebhookUpdate | null;
1112
+ recording?: AgentLifecycleWebhookUpdate | null;
1113
+ }
1114
+ /**
1115
+ * Everything a workspace webhook endpoint can subscribe to.
1116
+ *
1117
+ * Two families, and they behave differently on the wire:
1118
+ *
1119
+ * **AI voice-session events** (`call.pre_call` … `call.recording`) describe one
1120
+ * `voice_session` as it progresses, and carry a `session_id`.
1121
+ *
1122
+ * **Programmable-voice control events** (`call.initiated` … `call.hangup`) are
1123
+ * the webhook projection of the human-calling event stream — the same events
1124
+ * {@link CallControl.events} returns. A human call is not a `voice_session`, so
1125
+ * there is no session id to correlate on: the payload carries `call_id`,
1126
+ * `control_id` (null for call-scoped events that belong to no single leg),
1127
+ * `event_id` and `occurred_at`, merged with the event's own payload, and
1128
+ * `call_id` is the correlation key.
1129
+ *
1130
+ * Control events are delivered **once, without automatic retry**. Only
1131
+ * `call.report`, `call.analysis` and `call.recording` are durable — for those, a
1132
+ * failed delivery is re-attempted on a backoff. A control event that misses its
1133
+ * endpoint is gone from the webhook feed; the call's own event history
1134
+ * ({@link CallControl.events}) is the durable record, so reconcile from there
1135
+ * rather than treating the webhook as a queue.
1136
+ */
1137
+ export type WorkspaceWebhookEventType = 'call.pre_call' | 'call.status' | 'call.report' | 'call.analysis' | 'call.recording' | 'call.initiated' | 'call.ringing' | 'call.answered' | 'call.bridged' | 'call.hold' | 'call.unhold' | 'call.mute' | 'call.unmute' | 'call.dtmf.sent' | 'call.transfer.initiated' | 'call.transfer.completed' | 'call.transfer.failed' | 'call.leg.hangup' | 'call.hangup' | 'sms.received' | 'sms.accepted' | 'sms.sent' | 'sms.delivered' | 'sms.delivery_failed' | 'sms.submission_unknown' | 'sms.opted_out' | 'sms.opted_in';
1138
+ export type WebhookEventType = WorkspaceWebhookEventType | 'imessage.received' | 'imessage.reaction_received' | 'imessage.sent' | 'imessage.delivered' | 'imessage.delivery_failed';
1139
+ export type WebhookDeliveryStatus = 'pending' | 'delivering' | 'succeeded' | 'failed' | 'cancelled' | 'expired';
1140
+ export interface WebhookEndpointAuthHeaderInput {
1141
+ name: string;
1142
+ /** Write-only plaintext. The server encrypts it and never returns it. */
1143
+ value: string;
1144
+ }
1145
+ export interface WebhookEndpointAuthHeaderUpdate {
1146
+ name: string;
1147
+ /** Supply to set or rotate; omit to retain the stored value for this header name. */
1148
+ value?: string;
1149
+ }
1150
+ export interface WebhookEndpointInput {
1151
+ name: string;
1152
+ url: string;
1153
+ events: WorkspaceWebhookEventType[];
1154
+ /** Defaults to true. When false, agentIds must contain at least one agent. */
1155
+ allAgents?: boolean;
1156
+ agentIds?: string[];
1157
+ filterTags?: Record<string, string>;
1158
+ headers?: Record<string, string>;
1159
+ authHeaders?: WebhookEndpointAuthHeaderInput[];
1160
+ timeoutMs?: number;
1161
+ signingSecretSource?: 'workspace' | 'custom';
1162
+ /** Write-only. Required when signingSecretSource is custom. */
1163
+ signingSecret?: string;
1164
+ extractionFields?: AgentExtractionField[];
1165
+ /** Include message text, or emit metadata-only SMS payloads. Defaults to full. */
1166
+ contentMode?: 'full' | 'metadata_only';
1167
+ }
1168
+ export type WebhookEndpointUpdate = Partial<Omit<WebhookEndpointInput, 'authHeaders'>> & {
1169
+ authHeaders?: WebhookEndpointAuthHeaderUpdate[];
1170
+ };
1171
+ export interface WebhookEndpoint {
1172
+ id: string;
1173
+ name: string;
1174
+ url: string;
1175
+ events: WorkspaceWebhookEventType[];
1176
+ allAgents: boolean;
1177
+ agentIds: string[];
1178
+ filterTags: Record<string, string>;
1179
+ headers: Record<string, string>;
1180
+ authHeaders: Array<{
1181
+ name: string;
1182
+ configured: true;
1183
+ }>;
1184
+ timeoutMs: number;
1185
+ signingSecretSource: 'workspace' | 'custom';
1186
+ hasCustomSigningSecret: boolean;
1187
+ extractionFields: AgentExtractionField[];
1188
+ contentMode: 'full' | 'metadata_only';
1189
+ legacyManaged: boolean;
1190
+ createdAt: string;
1191
+ updatedAt: string;
1192
+ }
1193
+ export interface WebhookDeliveryListParams {
1194
+ endpointId?: string;
1195
+ event?: WebhookEventType;
1196
+ agentId?: string;
1197
+ status?: WebhookDeliveryStatus;
1198
+ sessionId?: string;
1199
+ eventId?: string;
1200
+ from?: string;
1201
+ to?: string;
1202
+ cursor?: string;
1203
+ limit?: number;
1204
+ }
1205
+ export interface WebhookDelivery {
1206
+ id: string;
1207
+ eventId: string;
1208
+ endpointId: string;
1209
+ endpointName: string;
1210
+ endpointKind: 'workspace' | 'imessage';
1211
+ endpointDeleted: boolean;
1212
+ event: WebhookEventType;
1213
+ sessionId: string | null;
1214
+ agentId: string | null;
1215
+ webhookTags: Record<string, string>;
1216
+ status: WebhookDeliveryStatus;
1217
+ attempts: number;
1218
+ httpStatus: number | null;
1219
+ error: string | null;
1220
+ occurredAt: string;
1221
+ expiresAt: string;
1222
+ deliveredAt: string | null;
1223
+ createdAt: string;
1224
+ /** False for provider-retried iMessage subscriber deliveries. */
1225
+ canRedeliver: boolean;
1226
+ }
1227
+ export interface WebhookDeliveryEndpointOption {
1228
+ id: string;
1229
+ name: string;
1230
+ kind: 'workspace' | 'imessage';
1231
+ deleted: boolean;
1232
+ }
1233
+ export interface WebhookDeliveryPage {
1234
+ data: WebhookDelivery[];
1235
+ nextCursor: string | null;
1236
+ endpointOptions: WebhookDeliveryEndpointOption[];
1237
+ }
1238
+ export interface WebhookDeliveryAttempt {
1239
+ id: string;
1240
+ attemptNumber: number;
1241
+ trigger: 'automatic' | 'manual';
1242
+ requestUrl: string;
1243
+ requestHeaders: Record<string, string>;
1244
+ requestBody: Record<string, unknown>;
1245
+ responseStatus: number | null;
1246
+ responseBody: string | null;
1247
+ responseTruncated: boolean;
1248
+ durationMs: number;
1249
+ error: string | null;
1250
+ createdAt: string;
1251
+ }
1252
+ export interface WebhookDeliveryDetail extends Omit<WebhookDelivery, 'attempts'> {
1253
+ attemptCount: number;
1254
+ requestPayload: Record<string, unknown>;
1255
+ attempts: WebhookDeliveryAttempt[];
1256
+ }
1257
+ /**
1258
+ * One prompt-variable registry entry. `defaultValue` fills the variable when a
1259
+ * session/dial call omits it (empty string = declared optional: renders blank
1260
+ * and `{% if %}` branches false). Without a default the variable is required
1261
+ * per call — omitting it fails session create with 400
1262
+ * `MISSING_TEMPLATE_VARIABLES`. Names may not use the reserved `system.`
1263
+ * namespace.
1264
+ */
1265
+ export interface AgentPromptVariable {
1266
+ name: string;
1267
+ defaultValue?: string;
1268
+ description?: string;
1269
+ }
1270
+ /** Turn-taking configuration for cascaded agents. Realtime agents ignore VAD. */
1271
+ export interface AgentTurnHandling {
1272
+ /** Local VAD provider. Omit to use Silero. */
1273
+ vad?: {
1274
+ provider: 'silero' | 'ai-coustics';
1275
+ };
1276
+ /**
1277
+ * Caller-leg input enhancement (ai-coustics). Omit for the platform default;
1278
+ * `enabled: false` turns it off; `model` is plain Quail (default) or Quail Voice
1279
+ * Focus (primary-speaker isolation, explicit opt-in).
1280
+ */
1281
+ noiseCancellation?: {
1282
+ enabled: boolean;
1283
+ model?: 'quail' | 'quail-voice-focus';
1284
+ };
1285
+ profile?: 'conversational' | 'ivr' | 'ivr_patient';
1286
+ endpointing?: {
1287
+ minDelay?: number;
1288
+ maxDelay?: number;
1289
+ };
1290
+ interruption?: {
1291
+ mode?: 'adaptive' | 'vad';
1292
+ minDuration?: number;
1293
+ minWords?: number;
1294
+ };
1295
+ turnDetection?: boolean | 'stt';
1296
+ contextThreshold?: boolean;
1297
+ textGate?: boolean;
1298
+ turnDetector?: 'smart_turn' | 'speko_turn_v1';
1299
+ dtmfToolDescription?: string;
1300
+ amdPrompt?: string;
1301
+ waitForCallee?: boolean;
1302
+ onMachine?: 'hangup' | 'leave_message' | 'agent_decides';
1303
+ voicemailMessage?: string;
785
1304
  }
786
1305
  export interface AgentRow {
787
1306
  id: string;
@@ -795,7 +1314,17 @@ export interface AgentRow {
795
1314
  sttOptions: AgentSttOptions | null;
796
1315
  backgroundAudio: AgentBackgroundAudio | null;
797
1316
  speechNormalization: AgentSpeechNormalization | null;
1317
+ turnHandling: AgentTurnHandling | null;
1318
+ /** @deprecated Use organization-owned `speko.webhooks` endpoints. */
798
1319
  webhooks: AgentWebhooksSerialized | null;
1320
+ /**
1321
+ * Post-call extraction schema on the agent itself — no webhook required.
1322
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1323
+ * definition wins on a name collision.
1324
+ */
1325
+ extractionFields: AgentExtractionField[];
1326
+ /** Prompt-variable registry. Returned on single-agent reads; null = empty. */
1327
+ promptVariables?: AgentPromptVariable[] | null;
799
1328
  createdAt: string;
800
1329
  updatedAt: string;
801
1330
  }
@@ -809,10 +1338,30 @@ export interface AgentCreateParams {
809
1338
  sttOptions?: AgentSttOptions;
810
1339
  backgroundAudio?: AgentBackgroundAudio;
811
1340
  speechNormalization?: AgentSpeechNormalization;
1341
+ turnHandling?: AgentTurnHandling;
1342
+ /** @deprecated Use `speko.webhooks.create()` after creating the agent. */
812
1343
  webhooks?: AgentWebhooksCreate;
1344
+ /**
1345
+ * Post-call extraction schema on the agent itself — no webhook required.
1346
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1347
+ * definition wins on a name collision.
1348
+ */
1349
+ extractionFields?: AgentExtractionField[];
1350
+ /** Declare the prompt's `{{variables}}` with per-agent defaults/descriptions. */
1351
+ promptVariables?: AgentPromptVariable[];
813
1352
  }
814
- export type AgentUpdateParams = Partial<Omit<AgentCreateParams, 'webhooks'>> & {
1353
+ export type AgentUpdateParams = Partial<Omit<AgentCreateParams, 'webhooks' | 'turnHandling'>> & {
1354
+ /** Set to null to clear all stored turn-taking overrides. */
1355
+ turnHandling?: AgentTurnHandling | null;
1356
+ /** @deprecated Use `speko.webhooks.update()` for organization-owned endpoints. */
815
1357
  webhooks?: AgentWebhooksUpdate | null;
1358
+ /**
1359
+ * Post-call extraction schema on the agent itself — no webhook required.
1360
+ * Merged with `webhooks.postCall.extractionFields`; the agent-level
1361
+ * definition wins on a name collision.
1362
+ */
1363
+ /** `null` clears the schema. */
1364
+ extractionFields?: AgentExtractionField[] | null;
816
1365
  };
817
1366
  export interface CallTranscriptEntry {
818
1367
  id: string;
@@ -824,6 +1373,11 @@ export interface CallTranscriptEntry {
824
1373
  provider: string | null;
825
1374
  model: string | null;
826
1375
  metadata: Record<string, unknown>;
1376
+ eou_ms?: number | null;
1377
+ llm_ttft_ms?: number | null;
1378
+ tts_ttfb_ms?: number | null;
1379
+ latency_status?: 'partial' | 'complete' | 'interrupted' | 'error' | null;
1380
+ conversational_latency_ms?: number | null;
827
1381
  }
828
1382
  export interface CallCostLine {
829
1383
  provider: string;
@@ -832,12 +1386,26 @@ export interface CallCostLine {
832
1386
  keySource: KeySource;
833
1387
  costMicroUsd: string;
834
1388
  }
1389
+ export interface CallReportWebhookDelivery {
1390
+ endpointId: string;
1391
+ deliveryId: string;
1392
+ eventId: string;
1393
+ delivered: boolean;
1394
+ status: number | null;
1395
+ error: string | null;
1396
+ createdAt: string;
1397
+ }
835
1398
  export interface CallReport {
836
1399
  session_id: string;
837
1400
  organization_id: string;
838
1401
  summary: string;
839
1402
  outcome: string;
840
1403
  structured_data: Record<string, unknown>;
1404
+ /**
1405
+ * Caller-defined extraction values, keyed by field name — the same object the
1406
+ * `call.report` webhook delivers. `{}` when the agent declares no fields.
1407
+ */
1408
+ custom_data: Record<string, unknown>;
841
1409
  transcript: {
842
1410
  entries: CallTranscriptEntry[];
843
1411
  };
@@ -851,11 +1419,18 @@ export interface CallReport {
851
1419
  analysis_model: string | null;
852
1420
  analysis_error: string | null;
853
1421
  analysis_completed_at: string | null;
1422
+ /** @deprecated Aggregate retained through the current SDK major version. */
854
1423
  post_call_webhook_status: 'not_configured' | 'pending' | 'delivered' | 'failed';
1424
+ /** @deprecated Aggregate retained through the current SDK major version. */
855
1425
  post_call_webhook_attempts: number;
1426
+ /** @deprecated Aggregate retained through the current SDK major version. */
856
1427
  post_call_webhook_next_retry_at: string | null;
1428
+ /** @deprecated Aggregate retained through the current SDK major version. */
857
1429
  post_call_webhook_delivered_at: string | null;
1430
+ /** @deprecated Aggregate retained through the current SDK major version. */
858
1431
  post_call_webhook_error: string | null;
1432
+ /** Canonical per-endpoint results; singular post-call fields are deprecated aggregates. */
1433
+ webhook_deliveries: CallReportWebhookDelivery[];
859
1434
  created_at: string;
860
1435
  updated_at: string;
861
1436
  }
@@ -900,11 +1475,35 @@ export interface FinalizeCallReportResult {
900
1475
  summary: string;
901
1476
  outcome: string;
902
1477
  cost_micro_usd: string;
1478
+ /** @deprecated Aggregate retained through the current SDK major version. */
903
1479
  webhook: unknown;
1480
+ webhook_deliveries: CallReportWebhookDelivery[];
904
1481
  }
905
1482
  export interface CallRecording {
906
1483
  url: string;
907
1484
  }
1485
+ export interface WebJoinParams {
1486
+ /** Display name other participants (and transcripts) see for the joiner. */
1487
+ displayName?: string;
1488
+ }
1489
+ export interface WebJoinResult {
1490
+ /** LiveKit access token for the live call's room. Mint at click time — short TTL. */
1491
+ token: string;
1492
+ /** Public LiveKit URL the browser connects to (pass both to `@spekoai/client`). */
1493
+ url: string;
1494
+ /** Participant identity minted for this join (unique per join). */
1495
+ identity: string;
1496
+ roomName: string;
1497
+ /** ISO timestamp the token stops being accepted for NEW connections. */
1498
+ expiresAt: string;
1499
+ }
1500
+ export interface EndCallResult {
1501
+ ok: true;
1502
+ /** `ending` when teardown was requested; `already_ended` when the call was over. */
1503
+ status: 'ending' | 'already_ended';
1504
+ /** ISO timestamp, present only with `already_ended`. */
1505
+ ended_at?: string;
1506
+ }
908
1507
  export interface CallEvent {
909
1508
  id: string;
910
1509
  session_id: string | null;
@@ -1056,6 +1655,44 @@ export interface AgentCallListPage {
1056
1655
  export interface AgentToolSourceInline {
1057
1656
  kind: 'inline';
1058
1657
  }
1658
+ /**
1659
+ * HTTP verb for a webhook tool. Omitting it means `POST`, so a tool
1660
+ * written before this field existed is unchanged.
1661
+ *
1662
+ * `GET` and `DELETE` send NO request body — not a `body` template and not
1663
+ * the default envelope either. Pairing one with `body` is rejected with
1664
+ * `422 WEBHOOK_TEMPLATE_INVALID` rather than silently dropping the body.
1665
+ */
1666
+ export type AgentToolWebhookMethod = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'GET';
1667
+ /**
1668
+ * JSON body template for a webhook tool.
1669
+ *
1670
+ * Omit it and Speko sends its fixed envelope —
1671
+ * `{tool, args, idempotency_key, session_id, tool_call_id}` — unchanged.
1672
+ * Supply one and it REPLACES that envelope, so a tool can post the shape
1673
+ * a third-party API actually wants.
1674
+ *
1675
+ * Every string leaf interpolates `{{name}}`: any session `variables` or
1676
+ * `toolSecrets` entry, plus these reserved names —
1677
+ *
1678
+ * | Name | Substitutes |
1679
+ * | --------------------- | ---------------------------------------------------- |
1680
+ * | `{{tool_name}}` | the tool's name |
1681
+ * | `{{session_id}}` | the session id |
1682
+ * | `{{tool_call_id}}` | the model's tool-call id |
1683
+ * | `{{idempotency_key}}` | `<session_id>:<tool_call_id>` |
1684
+ * | `{{args.<name>}}` | one argument, JSON type preserved |
1685
+ * | `{{args}}` | the whole arguments object — whole values only |
1686
+ *
1687
+ * Reserved names win over a session value of the same name. `{{args}}`
1688
+ * must be the ENTIRE value of a key; embedding it in a longer string is
1689
+ * rejected at write time instead of being JSON-stringified into it.
1690
+ *
1691
+ * Limits: 8 KB serialized, 8 levels of nesting, and no `__proto__`,
1692
+ * `constructor` or `prototype` key. A URL whose ORIGIN is templated may
1693
+ * not carry a body template at all — see the tool-calling guide.
1694
+ */
1695
+ export type AgentToolWebhookBody = Record<string, unknown> | unknown[];
1059
1696
  /**
1060
1697
  * Webhook source as sent to {@link AgentTools.create}. The plaintext
1061
1698
  * `secret` is encrypted server-side; the returned row carries
@@ -1067,6 +1704,12 @@ export interface AgentToolSourceWebhookCreate {
1067
1704
  /** Plaintext shared secret. Encrypted server-side at write time. */
1068
1705
  secret: string;
1069
1706
  headers?: Record<string, string>;
1707
+ /** Secret-referenced outbound auth headers (e.g. a Bearer token your endpoint requires). */
1708
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1709
+ /** HTTP verb. Omit for `POST`. `GET`/`DELETE` send no body and reject `body`. */
1710
+ method?: AgentToolWebhookMethod;
1711
+ /** JSON body template replacing the default envelope. See {@link AgentToolWebhookBody}. */
1712
+ body?: AgentToolWebhookBody;
1070
1713
  timeoutMs?: number;
1071
1714
  }
1072
1715
  /**
@@ -1079,6 +1722,12 @@ export interface AgentToolSourceWebhookSerialized {
1079
1722
  /** Pointer into Speko's secrets store. */
1080
1723
  secretRef: string;
1081
1724
  headers?: Record<string, string>;
1725
+ /** Outbound auth-header pointers; values stay encrypted server-side. */
1726
+ authHeaders?: AgentWebhookAuthHeader[];
1727
+ /** Absent means `POST`. */
1728
+ method?: AgentToolWebhookMethod;
1729
+ /** The stored body template. Configuration, not a secret — returned as saved. */
1730
+ body?: AgentToolWebhookBody;
1082
1731
  timeoutMs?: number;
1083
1732
  }
1084
1733
  export interface AgentToolSourceBuiltin {
@@ -1110,6 +1759,12 @@ export interface AgentToolSourceWebhookUpdate {
1110
1759
  /** Plaintext shared secret. Omit to keep the existing stored secret; supply to rotate. */
1111
1760
  secret?: string;
1112
1761
  headers?: Record<string, string>;
1762
+ /** Secret-referenced outbound auth headers. Replaces the stored set; omit a `value` to keep it. */
1763
+ authHeaders?: AgentWebhookAuthHeaderInput[];
1764
+ /** HTTP verb. Omit for `POST`. `GET`/`DELETE` send no body and reject `body`. */
1765
+ method?: AgentToolWebhookMethod;
1766
+ /** JSON body template replacing the default envelope. See {@link AgentToolWebhookBody}. */
1767
+ body?: AgentToolWebhookBody;
1113
1768
  timeoutMs?: number;
1114
1769
  }
1115
1770
  export type AgentToolSourceCreate = AgentToolSourceInline | AgentToolSourceWebhookCreate | AgentToolSourceBuiltin | AgentToolSourceIntegration;
@@ -1122,6 +1777,8 @@ export interface AgentToolRow {
1122
1777
  description: string;
1123
1778
  parameters: Record<string, unknown>;
1124
1779
  source: AgentToolSourceSerialized;
1780
+ /** Spoken lead-in behavior before this tool executes. */
1781
+ preToolSpeech: ChatToolPreToolSpeech;
1125
1782
  createdAt: string;
1126
1783
  updatedAt: string;
1127
1784
  }
@@ -1130,11 +1787,14 @@ export interface AgentToolCreateParams {
1130
1787
  description: string;
1131
1788
  parameters: Record<string, unknown>;
1132
1789
  source: AgentToolSourceCreate;
1790
+ /** Spoken lead-in behavior before the tool executes. Defaults to `auto`. */
1791
+ preToolSpeech?: ChatToolPreToolSpeech;
1133
1792
  }
1134
1793
  export interface AgentToolUpdateParams {
1135
1794
  description?: string;
1136
1795
  parameters?: Record<string, unknown>;
1137
1796
  source?: AgentToolSourceUpdate;
1797
+ preToolSpeech?: ChatToolPreToolSpeech;
1138
1798
  }
1139
1799
  export interface KnowledgeBaseRow {
1140
1800
  id: string;
@@ -1207,4 +1867,279 @@ export interface KnowledgeBaseDocumentPollOptions {
1207
1867
  /** Total timeout in milliseconds. Default 120000 (2 min). */
1208
1868
  timeoutMs?: number;
1209
1869
  }
1870
+ /**
1871
+ * Parameters for {@link CallControl.dial} — an outbound PSTN call placed by a
1872
+ * human broker, not by an AI agent. For an agent dial see
1873
+ * {@link VoiceDialParams}.
1874
+ */
1875
+ export interface CallControlDialParams {
1876
+ /** Destination in E.164 format (e.g. "+12015551234"). */
1877
+ to: string;
1878
+ /**
1879
+ * Caller ID to present, E.164. Must be a number your org owns; falls back to
1880
+ * the org's default outbound number when omitted.
1881
+ */
1882
+ from?: string;
1883
+ /** Opaque key/values stored on the call and echoed back on every read. */
1884
+ metadata?: Record<string, unknown>;
1885
+ }
1886
+ /**
1887
+ * What {@link CallControl.dial} resolves to. The join credentials come back
1888
+ * *with* the call rather than from a second request, because the dialing
1889
+ * broker's softphone has to already be in the room when the far end answers —
1890
+ * fetch them afterwards and the first moments of the call are silence.
1891
+ *
1892
+ * Destructure both halves; `call` alone is not enough to be heard:
1893
+ *
1894
+ * ```ts
1895
+ * const { call, join } = await speko.callControl.dial({ to: '+12015551234' });
1896
+ * ```
1897
+ */
1898
+ export interface CallControlDialResult {
1899
+ /** The call and both of its legs — the `controlId`s every later command needs. */
1900
+ readonly call: CallResource;
1901
+ /** Room credentials for the dialing broker's own browser leg. */
1902
+ readonly join: CallJoinCredentials;
1903
+ }
1904
+ /** Filters for {@link CallControl.list}. All optional; all AND-ed together. */
1905
+ export interface CallControlListParams {
1906
+ /**
1907
+ * Typed against the contract's `CallStatus` on purpose: the server validates
1908
+ * `?status=` against the same enum and rejects anything else, so a typo is a
1909
+ * compile error here instead of a `VALIDATION_ERROR` at runtime.
1910
+ */
1911
+ status?: CallStatus;
1912
+ direction?: CallDirection;
1913
+ /**
1914
+ * Only calls with a browser leg owned by this broker. Unlike dialing, reading
1915
+ * another broker's calls is allowed — a supervisor view is a legitimate use of
1916
+ * an org-scoped credential.
1917
+ */
1918
+ brokerId?: string;
1919
+ /** Newest first. Server-side default and cap apply. */
1920
+ limit?: number;
1921
+ }
1922
+ export type SmsMessageStatus = 'queued' | 'scheduled' | 'submitting' | 'accepted' | 'sent' | 'delivered' | 'delivery_failed' | 'rejected' | 'submission_unknown' | 'canceled' | 'received';
1923
+ export type SmsMessageDirection = 'inbound' | 'outbound';
1924
+ export type SmsMessageOrigin = 'api' | 'dashboard' | 'agent_tool' | 'agent_auto_reply' | 'telnyx';
1925
+ export interface SmsSegmentEstimate {
1926
+ readonly encoding: 'gsm7' | 'ucs2';
1927
+ readonly segments: number;
1928
+ readonly units: number;
1929
+ readonly per_segment: number;
1930
+ }
1931
+ export interface SmsMessage {
1932
+ readonly id: string;
1933
+ readonly conversation_id: string;
1934
+ readonly batch_id: string | null;
1935
+ readonly from_phone_number_id: string;
1936
+ readonly direction: SmsMessageDirection;
1937
+ readonly origin: SmsMessageOrigin;
1938
+ readonly from: string;
1939
+ readonly to: string;
1940
+ readonly text: string | null;
1941
+ readonly campaign_id: string | null;
1942
+ readonly brand_id: string | null;
1943
+ readonly campaign_snapshot: Record<string, unknown> | null;
1944
+ readonly consent_id: string | null;
1945
+ readonly consent_basis: string | null;
1946
+ readonly recipient_timezone: string | null;
1947
+ readonly requested_send_at: string | null;
1948
+ readonly effective_send_at: string | null;
1949
+ readonly terminal_at: string | null;
1950
+ readonly status: SmsMessageStatus;
1951
+ readonly provider_status: string | null;
1952
+ readonly encoding: 'gsm7' | 'ucs2' | null;
1953
+ readonly estimated_segments: number;
1954
+ readonly segment_count: number;
1955
+ readonly estimated: SmsSegmentEstimate;
1956
+ readonly charged_micro_usd: string;
1957
+ readonly provider_cost_micro_usd: string | null;
1958
+ readonly metadata: Record<string, unknown>;
1959
+ readonly error: {
1960
+ readonly code: string;
1961
+ readonly detail: string | null;
1962
+ } | null;
1963
+ readonly created_at: string;
1964
+ readonly updated_at: string;
1965
+ }
1966
+ export interface SmsPage<T> {
1967
+ readonly data: T[];
1968
+ readonly next_cursor: string | null;
1969
+ }
1970
+ export interface SmsSendParams {
1971
+ readonly from_phone_number_id: string;
1972
+ readonly to: string;
1973
+ readonly text: string;
1974
+ readonly idempotencyKey: string;
1975
+ readonly send_at?: string;
1976
+ readonly consent_id?: string | null;
1977
+ readonly recipient_timezone?: string | null;
1978
+ readonly metadata?: Record<string, unknown>;
1979
+ }
1980
+ export interface SmsMessageListParams {
1981
+ readonly conversation_id?: string;
1982
+ readonly batch_id?: string;
1983
+ readonly from_phone_number_id?: string;
1984
+ readonly recipient?: string;
1985
+ readonly campaign_id?: string;
1986
+ readonly status?: SmsMessageStatus;
1987
+ readonly direction?: SmsMessageDirection;
1988
+ readonly origin?: SmsMessageOrigin;
1989
+ readonly created_after?: string;
1990
+ readonly created_before?: string;
1991
+ readonly cursor?: string;
1992
+ readonly limit?: number;
1993
+ }
1994
+ export interface SmsBatchRecipient {
1995
+ readonly to: string;
1996
+ readonly text: string;
1997
+ readonly consent_id?: string | null;
1998
+ readonly recipient_timezone?: string | null;
1999
+ readonly metadata?: Record<string, unknown>;
2000
+ }
2001
+ export interface SmsBatchCreateParams {
2002
+ readonly from_phone_number_id: string;
2003
+ readonly recipients: readonly SmsBatchRecipient[];
2004
+ readonly idempotencyKey: string;
2005
+ readonly send_at?: string;
2006
+ }
2007
+ export interface SmsBatch {
2008
+ readonly id: string;
2009
+ readonly from_phone_number_id: string;
2010
+ readonly status: 'queued' | 'scheduled' | 'processing' | 'completed' | 'partially_failed' | 'failed' | 'canceled';
2011
+ readonly requested_send_at: string | null;
2012
+ readonly total_count: number;
2013
+ readonly accepted_count: number;
2014
+ readonly rejected_count: number;
2015
+ readonly delivered_count: number;
2016
+ readonly failed_count: number;
2017
+ readonly canceled_at: string | null;
2018
+ readonly completed_at: string | null;
2019
+ readonly created_at: string;
2020
+ readonly updated_at: string;
2021
+ }
2022
+ export interface SmsConversation {
2023
+ readonly id: string;
2024
+ readonly phone_number_id: string;
2025
+ readonly remote_phone_number: string;
2026
+ readonly campaign_id: string | null;
2027
+ readonly campaign_snapshot: Record<string, unknown> | null;
2028
+ readonly status: 'open' | 'closed' | 'spam';
2029
+ readonly automation_status: 'disabled' | 'enabled' | 'paused';
2030
+ readonly assigned_user_id: string | null;
2031
+ readonly assigned_agent_id: string | null;
2032
+ readonly unread_count: number;
2033
+ readonly recipient_timezone: string | null;
2034
+ readonly last_inbound_at: string | null;
2035
+ readonly last_outbound_at: string | null;
2036
+ readonly last_message_at: string;
2037
+ readonly content_redacted_at: string | null;
2038
+ readonly created_at: string;
2039
+ readonly updated_at: string;
2040
+ }
2041
+ export interface SmsConversationListParams {
2042
+ readonly status?: SmsConversation['status'];
2043
+ readonly phone_number_id?: string;
2044
+ readonly assigned_user_id?: string;
2045
+ readonly assigned_agent_id?: string;
2046
+ readonly recipient?: string;
2047
+ readonly unread?: boolean;
2048
+ readonly cursor?: string;
2049
+ readonly limit?: number;
2050
+ }
2051
+ export interface SmsConversationUpdate {
2052
+ readonly status?: SmsConversation['status'];
2053
+ readonly assigned_user_id?: string | null;
2054
+ readonly assigned_agent_id?: string | null;
2055
+ readonly automation_status?: SmsConversation['automation_status'];
2056
+ readonly recipient_timezone?: string | null;
2057
+ }
2058
+ export interface SmsConversationSendParams {
2059
+ readonly text: string;
2060
+ readonly idempotencyKey: string;
2061
+ readonly send_at?: string;
2062
+ readonly consent_id?: string | null;
2063
+ readonly recipient_timezone?: string | null;
2064
+ readonly metadata?: Record<string, unknown>;
2065
+ }
2066
+ export type SmsConsentSource = 'inbound' | 'api' | 'keyword' | 'webform' | 'paper' | 'verbal' | 'import';
2067
+ export interface SmsConsentInput {
2068
+ readonly recipient: string;
2069
+ readonly campaign_id: string;
2070
+ readonly source: SmsConsentSource;
2071
+ readonly proof_reference?: string | null;
2072
+ readonly proof?: string | null;
2073
+ readonly timezone?: string | null;
2074
+ readonly captured_at?: string;
2075
+ readonly expires_at?: string | null;
2076
+ readonly metadata?: Record<string, unknown>;
2077
+ }
2078
+ export interface SmsConsent {
2079
+ readonly id: string;
2080
+ readonly recipient: string;
2081
+ readonly campaign_id: string;
2082
+ readonly status: 'active' | 'expired' | 'revoked';
2083
+ readonly source: SmsConsentSource;
2084
+ readonly proof_reference: string | null;
2085
+ readonly proof_hash: string | null;
2086
+ readonly timezone: string | null;
2087
+ readonly captured_at: string;
2088
+ readonly expires_at: string | null;
2089
+ readonly revoked_at: string | null;
2090
+ readonly revoked_reason: string | null;
2091
+ readonly metadata: Record<string, unknown>;
2092
+ readonly created_at: string;
2093
+ }
2094
+ export interface SmsConsentListParams {
2095
+ readonly recipient?: string;
2096
+ readonly campaign_id?: string;
2097
+ readonly status?: SmsConsent['status'];
2098
+ readonly limit?: number;
2099
+ }
2100
+ export interface SmsSuppression {
2101
+ readonly id: string;
2102
+ readonly recipient: string;
2103
+ readonly status: 'suppressed' | 'lifted';
2104
+ readonly keyword: string | null;
2105
+ readonly source_phone_number_id: string | null;
2106
+ readonly source_message_provider_id: string | null;
2107
+ readonly suppressed_at: string;
2108
+ readonly lifted_at: string | null;
2109
+ readonly updated_at: string;
2110
+ }
2111
+ export interface SmsSettings {
2112
+ readonly messaging_profile_id: string | null;
2113
+ readonly messaging_profile_status: string;
2114
+ readonly webhook_config_version: number;
2115
+ readonly opt_out_config_version: number;
2116
+ readonly help_message: string;
2117
+ readonly opt_out_message: string;
2118
+ readonly opt_in_message: string;
2119
+ readonly retention_days: number;
2120
+ readonly quiet_hours_start: string;
2121
+ readonly quiet_hours_end: string;
2122
+ readonly default_timezone: string | null;
2123
+ readonly default_automation_enabled: boolean;
2124
+ readonly last_synced_at: string | null;
2125
+ readonly last_error: string | null;
2126
+ readonly updated_at: string;
2127
+ }
2128
+ export type SmsSettingsUpdate = Partial<Pick<SmsSettings, 'help_message' | 'opt_out_message' | 'opt_in_message' | 'retention_days' | 'quiet_hours_start' | 'quiet_hours_end' | 'default_timezone' | 'default_automation_enabled'>>;
2129
+ export interface SmsConversationNote {
2130
+ readonly id: string;
2131
+ readonly conversation_id: string;
2132
+ readonly body: string | null;
2133
+ readonly created_by_user_id: string;
2134
+ readonly redacted_at: string | null;
2135
+ readonly created_at: string;
2136
+ }
2137
+ export interface SmsStreamEvent {
2138
+ readonly event: string;
2139
+ readonly id?: string;
2140
+ readonly message_id: string;
2141
+ readonly conversation_id: string;
2142
+ readonly status: SmsMessageStatus;
2143
+ readonly occurred_at: string;
2144
+ }
1210
2145
  //# sourceMappingURL=index.d.ts.map