@voctiv/agent-sdk 0.3.0 → 0.3.1

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
@@ -81,6 +81,7 @@ The [`examples/`](./examples/) folder contains copy-paste-ready scripts:
81
81
  | [schedule-call-with-defaults.ts](./examples/schedule-call-with-defaults.ts) | `platform.call()` without explicit recall — CMS defaults from `context` |
82
82
  | [after-call-continuation.ts](./examples/after-call-continuation.ts) | `onSuccessCall` / `onFailedCall` vs recall (mutually exclusive) |
83
83
  | [read-recall-from-params.ts](./examples/read-recall-from-params.ts) | `parseRecallDelaySeconds()` / `parseRecallCount()` on legacy params |
84
+ | [custom-media-providers.ts](./examples/custom-media-providers.ts) | `defineMediaProviders` + custom ASR/TTS via `createAsr` / `createTts` |
84
85
 
85
86
  ### Quick recall example
86
87
 
@@ -116,6 +117,8 @@ const maxAttempts = parseRecallCount(context.dialogParams?.recall_count);
116
117
 
117
118
  `defineScript(fn)` marks the default export as the script entry point. It returns the same function and exists to give TypeScript the correct `ScriptContext` shape.
118
119
 
120
+ `defineMediaProviders(def)` marks a named `mediaProviders` export for custom ASR/TTS factories (trusted packages only). See [Custom ASR / TTS Providers](#custom-asr--tts-providers).
121
+
119
122
  **Three different “context” names:**
120
123
 
121
124
  | Name | Meaning |
@@ -554,6 +557,9 @@ All audio playback goes through **`channel.audio`** (`ChannelAudio`). There are
554
557
  Create a reusable TTS session with `channel.createTts(config?)` — same pattern as `createAsr`.
555
558
  Call `tts.say` / `tts.say$` / `tts.presay` on the handle so synthesis reuses the warmed SSL / streaming WebSocket.
556
559
 
560
+ Custom engines from a logic package are documented in
561
+ [Custom ASR / TTS Providers](#custom-asr--tts-providers).
562
+
557
563
  ```ts
558
564
  const tts = await channel.createTts({
559
565
  vendor: 'elevenlabs',
@@ -774,6 +780,202 @@ Shared by `say()`, `play()`, and (where noted) `presay()`:
774
780
 
775
781
  `play()` ignores `tts*` and `cache` for raw audio. `preload()` only accepts `cache` overrides.
776
782
 
783
+ ## Custom ASR / TTS Providers
784
+
785
+ Trusted logic packages can ship their own ASR/TTS engines next to `defineScript`. The host
786
+ `require`s a static **`export const mediaProviders`** **outside** the script sandbox (same
787
+ process privileges as the API). Use this only for packages you trust.
788
+
789
+ Mid-script `registerAsr` / `registerTts` is **not** supported — declare vendors at module load.
790
+
791
+ ### Export shape
792
+
793
+ ```ts
794
+ import {
795
+ defineScript,
796
+ defineMediaProviders,
797
+ type MediaConnectorContext,
798
+ type MediaProviderShared,
799
+ type ScriptAsrConnector,
800
+ type ScriptTtsConnector,
801
+ } from '@voctiv/agent-sdk';
802
+ import { Subject } from 'rxjs';
803
+ import { Readable } from 'stream';
804
+
805
+ export const mediaProviders = defineMediaProviders({
806
+ // Optional: one object per dialog, shared by ASR + TTS factories
807
+ createShared: (ctx) => ({
808
+ dialogUuid: ctx.dialogUuid,
809
+ dispose() { /* release app state */ },
810
+ }),
811
+ asr: {
812
+ 'my-asr': (ctx, shared) => new MyAsr(ctx, shared),
813
+ },
814
+ tts: {
815
+ 'my-tts': (ctx, shared) => new MyTts(ctx, shared),
816
+ },
817
+ });
818
+
819
+ export default defineScript(async ({ channel }) => {
820
+ const asr = await channel.createAsr({
821
+ vendor: 'my-asr',
822
+ language: 'ru-RU',
823
+ data: { api_key: process.env.MY_ASR_KEY! },
824
+ });
825
+ const tts = await channel.createTts({
826
+ vendor: 'my-tts',
827
+ data: {
828
+ api_key: process.env.MY_TTS_KEY!,
829
+ voice_id: '…',
830
+ output_format: 'pcm_16000', // preferred for telephony
831
+ },
832
+ });
833
+
834
+ await tts.say('Hello from a custom connector.', {
835
+ alias: 'greet',
836
+ ttsStrategy: 'streaming', // used when supportsStreaming() === true
837
+ });
838
+
839
+ tts.say$('One. Two.', { alias: 'reply' }).subscribe((ev) => {
840
+ // queued | speaking | done | cancelled — same events as builtin TTS
841
+ });
842
+
843
+ // Reuse the warm handle:
844
+ await channel.audio.say('Again', { tts });
845
+
846
+ asr.destroy();
847
+ tts.destroy();
848
+ });
849
+ ```
850
+
851
+ `defineMediaProviders` is an identity helper for typing. The host looks up
852
+ `module.exports.mediaProviders` (or `default.mediaProviders`) on the script entry file.
853
+
854
+ Full stub: [`examples/custom-media-providers.ts`](./examples/custom-media-providers.ts).
855
+
856
+ ### Implement ASR (`ScriptAsrConnector`)
857
+
858
+ The host pushes **PCM S16LE mono 16 kHz** frames into `send()`. Emit partials on
859
+ `transcription$` and finals on both `transcription$` (`isFinal: true`) and `result$`.
860
+
861
+ | Member | Role |
862
+ | --- | --- |
863
+ | `transcription$` | `{ text, isFinal }` partials and finals |
864
+ | `result$` | Final utterance strings (drives `AsrHandle.result$`) |
865
+ | `error$` | Vendor / transport failures |
866
+ | `send(audio)` | Accept inbound PCM (`ArrayBuffer` / `Buffer`) |
867
+ | `speech(active)` | Optional VAD gate from the host (`true` = utterance open) |
868
+ | `finalize()` | End-of-utterance nudge (flush / commit) |
869
+ | `isOpen()` | Whether the vendor socket is ready |
870
+ | `close()` | Tear down; complete Subjects |
871
+
872
+ ```ts
873
+ class MyAsr implements ScriptAsrConnector {
874
+ readonly transcription$ = new Subject<{ text: string; isFinal: boolean }>();
875
+ readonly result$ = new Subject<string>();
876
+ readonly error$ = new Subject<{ message: string; code?: number }>();
877
+
878
+ constructor(
879
+ private readonly ctx: MediaConnectorContext,
880
+ private readonly shared: MediaProviderShared | undefined,
881
+ ) {
882
+ // ctx.config — flattened createAsr data + channel asrConfig
883
+ // ctx.dialogUuid, ctx.role, ctx.debug$
884
+ }
885
+
886
+ isOpen() { return true; }
887
+ send(audio: ArrayBufferLike) { /* forward PCM to vendor */ }
888
+ speech(_active: boolean) {}
889
+ finalize() {
890
+ const text = '…';
891
+ this.transcription$.next({ text, isFinal: true });
892
+ this.result$.next(text);
893
+ }
894
+ close() {
895
+ this.transcription$.complete();
896
+ this.result$.complete();
897
+ this.error$.complete();
898
+ }
899
+ }
900
+ ```
901
+
902
+ Factory `throw` during create → host degraded ASR handle (same as builtin). Prefer connecting
903
+ lazily in `send()`; the host does not call a separate `connect()` on custom connectors.
904
+
905
+ ### Implement TTS (`ScriptTtsConnector`)
906
+
907
+ **One class per vendor** covers batch HTTP and optional streaming WebSocket. Do **not**
908
+ register a separate streaming map.
909
+
910
+ | Member | Role |
911
+ | --- | --- |
912
+ | `supportsStreaming()` | `true` → host may use WS path for `ttsStrategy: 'streaming'` |
913
+ | `textToSpeechStream(text, ctx?)` | Batch/HTTP synthesis → `Readable` of audio bytes |
914
+ | `audio$` / `done$` | Streaming audio chunks and end-of-generation |
915
+ | `open` / `startGeneration` / `sendText` / `flush` | Streaming lifecycle |
916
+ | `sendSeparatorFlush?()` | Optional sentence separator flush |
917
+ | `close()` | Soft-close sockets for **reuse** (do not complete Subjects if `open()` may run again) |
918
+
919
+ ```ts
920
+ class MyTts implements ScriptTtsConnector {
921
+ readonly audio$ = new Subject<Buffer>();
922
+ readonly done$ = new Subject<void>();
923
+
924
+ supportsStreaming() {
925
+ return true; // or false for HTTP-only
926
+ }
927
+
928
+ async textToSpeechStream(rawtext: string, _ctx?: { previousText?: string; nextText?: string }) {
929
+ // Return PCM (preferred) or MP3/OGG. Hint format via createTts data:
930
+ // output_format: 'pcm_16000' | 'mp3_…' or audioFormat: 'pcm' | 'mp3'
931
+ return Readable.from([/* bytes */]);
932
+ }
933
+
934
+ async open() { /* open long-lived WS */ }
935
+ async startGeneration() { /* BOS / new utterance */ }
936
+ sendText(chunk: string) { /* stream tokens */ }
937
+ flush() { /* EOS; later emit done$ */ }
938
+ close() { /* close WS; keep Subjects alive for fingerprint reuse */ }
939
+ }
940
+ ```
941
+
942
+ - `supportsStreaming() === false` → streaming strategy falls back to sentence/full HTTP path.
943
+ - Default audio assumption for custom vendors is **PCM** unless `output_format` / `audioFormat`
944
+ indicates a compressed format (`mp3`, `ogg`, …).
945
+
946
+ ### Using custom vendors in the script
947
+
948
+ ```ts
949
+ const asr = await channel.createAsr({ vendor: 'my-asr', data: { … } });
950
+ const tts = await channel.createTts({ vendor: 'my-tts', data: { … } });
951
+
952
+ await tts.say('Hi', { alias: 'greet' });
953
+ await tts.presay('Warm cache');
954
+ tts.say$('Next', { alias: 'reply', ttsStrategy: 'streaming' }).subscribe(…);
955
+
956
+ await channel.audio.say('Reuse', { tts }); // same warm session
957
+ ```
958
+
959
+ Vendor keys must be **multi-character** names. Builtin single-letter codes
960
+ (`A`, `D`, `E`, `ES`, `G`, `V`, `W`, `W2`, …) **cannot** be overridden.
961
+
962
+ ### Warm reuse vs `createShared` vs PCM cache
963
+
964
+ | Layer | What it caches | Scope |
965
+ | --- | --- | --- |
966
+ | Factory + session registries | One connector instance per `dialog + vendor + config fingerprint` (TCP/WS) | Host automatic |
967
+ | `createShared` | Your app/vendor state shared by ASR↔TTS factories | One object per dialog |
968
+ | `PlayOptions.cache` / `presay` | Synthesized PCM files | Host TTS file cache |
969
+
970
+ Call `createAsr` / `createTts` early to warm sockets. Repeated `say` with the same handle (or
971
+ matching fingerprint) must **not** open a new TCP/WS per utterance.
972
+
973
+ ### Security
974
+
975
+ - Host `require` of `mediaProviders` runs with API privileges — **trusted packages only**.
976
+ - Entry path is restricted to the script root (path traversal denied).
977
+ - Optional host allowlists may further restrict which packages may export providers.
978
+
777
979
  ## TTS Credentials And Vendor Parameters
778
980
 
779
981
  ### Direct TTS Vendor Parameters
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@
30
30
  */
31
31
  export { defineScript } from './define-script';
32
32
  export type { ScriptContext, ScriptFn } from './define-script';
33
+ export { defineMediaProviders } from './types/media-providers';
34
+ export type { MediaProvidersDefinition, MediaConnectorContext, MediaProviderShared, AsrProviderFactory, TtsProviderFactory, ScriptAsrConnector, ScriptAsrConnectorError, ScriptTtsConnector, TtsSynthesisContext as ScriptTtsSynthesisContext, } from './types/media-providers';
33
35
  export type { ScriptDialogContext, ScriptPhase, ScriptRunTime, ScriptResult, PersistedScriptResult, ScriptError, AgentContext, AgentEnvSetOptions, StorageContextApi, } from './types/script-context';
34
36
  export { getScriptPhase } from './types/script-context';
35
37
  export type { NluScriptApi, PlatformApi, DialogApi, ScheduleCallOptions, SendMessageOptions, InboundMessage, MessagingApi, } from './types/platform';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE/D,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,YAAY,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3H,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,SAAS,EACT,OAAO,EACP,SAAS,EACT,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,YAAY,EACV,wBAAwB,EACxB,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,IAAI,yBAAyB,GACjD,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,YAAY,EACV,YAAY,EACZ,WAAW,EACX,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,GACd,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,yBAAyB,GAC1B,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACjG,YAAY,EACV,SAAS,EACT,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC3H,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,YAAY,GACb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,SAAS,EACT,OAAO,EACP,SAAS,EACT,WAAW,GACZ,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -30,9 +30,11 @@
30
30
  * and live SIP INFO ({@link import('./types/events').SipInfo} on `sipInfo$`). See **`README.md`** → SIP Signalling Metadata.
31
31
  */
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.parseRecallDelaySeconds = exports.parseRecallCount = exports.isLegacyPhraseRecord = exports.LEGACY_PHRASE_RECORD_BRAND = exports.TranscriptionRole = exports.getScriptPhase = exports.defineScript = void 0;
33
+ exports.parseRecallDelaySeconds = exports.parseRecallCount = exports.isLegacyPhraseRecord = exports.LEGACY_PHRASE_RECORD_BRAND = exports.TranscriptionRole = exports.getScriptPhase = exports.defineMediaProviders = exports.defineScript = void 0;
34
34
  var define_script_1 = require("./define-script");
35
35
  Object.defineProperty(exports, "defineScript", { enumerable: true, get: function () { return define_script_1.defineScript; } });
36
+ var media_providers_1 = require("./types/media-providers");
37
+ Object.defineProperty(exports, "defineMediaProviders", { enumerable: true, get: function () { return media_providers_1.defineMediaProviders; } });
36
38
  var script_context_1 = require("./types/script-context");
37
39
  Object.defineProperty(exports, "getScriptPhase", { enumerable: true, get: function () { return script_context_1.getScriptPhase; } });
38
40
  var logger_1 = require("./types/logger");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAEH,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAerB,yDAAwD;AAA/C,gHAAA,cAAc,OAAA;AAavB,yCAAmD;AAA1C,2GAAA,iBAAiB,OAAA;AAwC1B,uDAG+B;AAF7B,2HAAA,0BAA0B,OAAA;AAC1B,qHAAA,oBAAoB,OAAA;AAUtB,+CAA2E;AAAlE,gHAAA,gBAAgB,OAAA;AAAE,uHAAA,uBAAuB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAEH,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAGrB,2DAA+D;AAAtD,uHAAA,oBAAoB,OAAA;AAyB7B,yDAAwD;AAA/C,gHAAA,cAAc,OAAA;AAavB,yCAAmD;AAA1C,2GAAA,iBAAiB,OAAA;AAwC1B,uDAG+B;AAF7B,2HAAA,0BAA0B,OAAA;AAC1B,qHAAA,oBAAoB,OAAA;AAUtB,+CAA2E;AAAlE,gHAAA,gBAAgB,OAAA;AAAE,uHAAA,uBAAuB,OAAA"}
@@ -0,0 +1,83 @@
1
+ import type { Observable, Subject } from 'rxjs';
2
+ import type { Readable } from 'stream';
3
+ /**
4
+ * Context passed to custom ASR/TTS factories from {@link defineMediaProviders}.
5
+ * Populated by the host outside the script sandbox.
6
+ */
7
+ export interface MediaConnectorContext {
8
+ id: string;
9
+ config: Record<string, string>;
10
+ dialogUuid: string;
11
+ role?: string;
12
+ debug$: Subject<string>;
13
+ }
14
+ export interface ScriptAsrConnectorError {
15
+ message: string;
16
+ code?: number;
17
+ }
18
+ /**
19
+ * Host ASR connector contract for custom providers (same surface as runtime AsrConnector).
20
+ */
21
+ export interface ScriptAsrConnector {
22
+ transcription$: Subject<{
23
+ text: string;
24
+ isFinal: boolean;
25
+ }>;
26
+ result$: Subject<string>;
27
+ error$: Subject<ScriptAsrConnectorError>;
28
+ isOpen(): boolean;
29
+ send(audio: ArrayBufferLike): void;
30
+ speech(active: boolean): void;
31
+ finalize(): void;
32
+ close(): void;
33
+ }
34
+ export type TtsSynthesisContext = {
35
+ previousText?: string;
36
+ nextText?: string;
37
+ };
38
+ /**
39
+ * Unified TTS connector: batch HTTP + optional streaming WebSocket methods.
40
+ */
41
+ export interface ScriptTtsConnector {
42
+ supportsStreaming(): boolean;
43
+ textToSpeechStream(rawtext: string, ctx?: TtsSynthesisContext): Promise<Readable>;
44
+ readonly audio$: Subject<Buffer>;
45
+ readonly done$: Subject<void>;
46
+ open(): Promise<void>;
47
+ startGeneration(): Promise<void>;
48
+ sendText(chunk: string): void;
49
+ flush(): void;
50
+ sendSeparatorFlush?(): void;
51
+ close(): void;
52
+ }
53
+ /** Optional session object shared by ASR and TTS factories for one dialog. */
54
+ export type MediaProviderShared = {
55
+ dispose?: () => void;
56
+ close?: () => void;
57
+ } & Record<string, unknown>;
58
+ export type AsrProviderFactory = (ctx: MediaConnectorContext, shared: MediaProviderShared | undefined) => ScriptAsrConnector;
59
+ export type TtsProviderFactory = (ctx: MediaConnectorContext, shared: MediaProviderShared | undefined) => ScriptTtsConnector;
60
+ export interface MediaProvidersDefinition {
61
+ createShared?: (ctx: {
62
+ dialogUuid: string;
63
+ debug$: Subject<string>;
64
+ }) => MediaProviderShared;
65
+ asr?: Record<string, AsrProviderFactory>;
66
+ tts?: Record<string, TtsProviderFactory>;
67
+ }
68
+ /**
69
+ * Mark a named export as custom media providers (identity helper).
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * export const mediaProviders = defineMediaProviders({
74
+ * createShared: (ctx) => new MySession(ctx),
75
+ * asr: { 'my-asr': (ctx, shared) => new MyAsr(ctx, shared) },
76
+ * tts: { 'my-tts': (ctx, shared) => new MyTts(ctx, shared) },
77
+ * });
78
+ * ```
79
+ */
80
+ export declare function defineMediaProviders(def: MediaProvidersDefinition): MediaProvidersDefinition;
81
+ /** @internal helper type for hosts that mirror connector subjects */
82
+ export type SubjectLike<T> = Subject<T> | Observable<T>;
83
+ //# sourceMappingURL=media-providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-providers.d.ts","sourceRoot":"","sources":["../../src/types/media-providers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineMediaProviders = defineMediaProviders;
4
+ /**
5
+ * Mark a named export as custom media providers (identity helper).
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * export const mediaProviders = defineMediaProviders({
10
+ * createShared: (ctx) => new MySession(ctx),
11
+ * asr: { 'my-asr': (ctx, shared) => new MyAsr(ctx, shared) },
12
+ * tts: { 'my-tts': (ctx, shared) => new MyTts(ctx, shared) },
13
+ * });
14
+ * ```
15
+ */
16
+ function defineMediaProviders(def) {
17
+ return def;
18
+ }
19
+ //# sourceMappingURL=media-providers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-providers.js","sourceRoot":"","sources":["../../src/types/media-providers.ts"],"names":[],"mappings":";;AAmGA,oDAIC;AAhBD;;;;;;;;;;;GAWG;AACH,SAAgB,oBAAoB,CAClC,GAA6B;IAE7B,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -11,6 +11,7 @@ Each file exports one `defineScript` handler. The host **always** invokes that s
11
11
  | [schedule-call-with-defaults.ts](./schedule-call-with-defaults.ts) | `platform.call()` using CMS defaults from `context` |
12
12
  | [after-call-continuation.ts](./after-call-continuation.ts) | `onSuccessCall` / `onFailedCall` + `getScriptPhase()` |
13
13
  | [read-recall-from-params.ts](./read-recall-from-params.ts) | `parseRecallDelaySeconds()` / `parseRecallCount()` |
14
+ | [custom-media-providers.ts](./custom-media-providers.ts) | `defineMediaProviders` + `createAsr` / `createTts` / `say$` |
14
15
 
15
16
  ## Recall flow
16
17
 
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Custom ASR / TTS from a trusted logic package.
3
+ *
4
+ * Export `mediaProviders` next to `defineScript`. The host loads that export
5
+ * outside the sandbox and wires vendors into `createAsr` / `createTts` with the
6
+ * same warm TCP/WS reuse as built-in engines.
7
+ *
8
+ * Batch and streaming TTS share one class (`supportsStreaming()` + streaming
9
+ * lifecycle methods). Do not register a separate streaming map.
10
+ */
11
+ import { Readable } from 'stream';
12
+ import { Subject } from 'rxjs';
13
+ import {
14
+ defineScript,
15
+ defineMediaProviders,
16
+ type MediaConnectorContext,
17
+ type MediaProviderShared,
18
+ type ScriptAsrConnector,
19
+ type ScriptTtsConnector,
20
+ } from '@voctiv/agent-sdk';
21
+
22
+ type SharedSession = MediaProviderShared & {
23
+ turns: number;
24
+ };
25
+
26
+ class EchoAsr implements ScriptAsrConnector {
27
+ readonly transcription$ = new Subject<{ text: string; isFinal: boolean }>();
28
+ readonly result$ = new Subject<string>();
29
+ readonly error$ = new Subject<{ message: string; code?: number }>();
30
+
31
+ constructor(
32
+ private readonly ctx: MediaConnectorContext,
33
+ private readonly shared: SharedSession | undefined,
34
+ ) {}
35
+
36
+ isOpen(): boolean {
37
+ return true;
38
+ }
39
+
40
+ send(_audio: ArrayBufferLike): void {
41
+ // Forward PCM to your vendor here.
42
+ }
43
+
44
+ speech(_active: boolean): void {}
45
+
46
+ finalize(): void {
47
+ const text = 'heard';
48
+ this.transcription$.next({ text, isFinal: true });
49
+ this.result$.next(text);
50
+ if (this.shared) this.shared.turns += 1;
51
+ }
52
+
53
+ close(): void {
54
+ this.transcription$.complete();
55
+ this.result$.complete();
56
+ this.error$.complete();
57
+ }
58
+ }
59
+
60
+ class BatchOrStreamTts implements ScriptTtsConnector {
61
+ readonly audio$ = new Subject<Buffer>();
62
+ readonly done$ = new Subject<void>();
63
+
64
+ constructor(
65
+ private readonly ctx: MediaConnectorContext,
66
+ private readonly shared: SharedSession | undefined,
67
+ ) {}
68
+
69
+ /** Return true when this connector also owns a long-lived streaming socket. */
70
+ supportsStreaming(): boolean {
71
+ return false;
72
+ }
73
+
74
+ async textToSpeechStream(rawtext: string): Promise<Readable> {
75
+ void this.ctx;
76
+ void this.shared;
77
+ // Replace with HTTP keep-alive synthesis that returns a PCM stream.
78
+ return Readable.from([Buffer.from(rawtext, 'utf8')]);
79
+ }
80
+
81
+ async open(): Promise<void> {}
82
+ async startGeneration(): Promise<void> {}
83
+ sendText(_chunk: string): void {}
84
+ flush(): void {}
85
+ close(): void {
86
+ // Soft-close streaming sockets for reuse; do not complete Subjects if the
87
+ // host may call open() again on the same fingerprint.
88
+ }
89
+ }
90
+
91
+ export const mediaProviders = defineMediaProviders({
92
+ createShared: (): SharedSession => ({
93
+ turns: 0,
94
+ dispose() {
95
+ /* release vendor app state for this dialog */
96
+ },
97
+ }),
98
+ asr: {
99
+ 'my-asr': (ctx, shared) => new EchoAsr(ctx, shared as SharedSession),
100
+ },
101
+ tts: {
102
+ 'my-tts': (ctx, shared) =>
103
+ new BatchOrStreamTts(ctx, shared as SharedSession),
104
+ },
105
+ });
106
+
107
+ export default defineScript(async ({ channel, logger }) => {
108
+ channel.sip.answer();
109
+
110
+ const asr = await channel.createAsr({ vendor: 'my-asr' });
111
+ const tts = await channel.createTts({ vendor: 'my-tts' });
112
+
113
+ await tts.say('Hello from a custom TTS connector.');
114
+
115
+ tts.say$('One. Two.', { alias: 'reply' }).subscribe({
116
+ next: (ev) => {
117
+ logger.log('tts utterance', { state: ev.state, alias: ev.itemAlias });
118
+ },
119
+ error: (err) => logger.error('tts failed', err),
120
+ });
121
+
122
+ // Same warm handle via audio.say:
123
+ await channel.audio.say('Reuse handle', { tts });
124
+
125
+ asr.destroy();
126
+ tts.destroy();
127
+ channel.sip.hangup();
128
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voctiv/agent-sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Voctiv TypeScript agent SDK: defineScript and platform types for the voice/dialog scripting runtime.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "",