@voctiv/agent-sdk 0.3.2 → 0.3.3

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
@@ -70,7 +70,15 @@ export default defineScript(async ({ channel, logger, context }) => {
70
70
  });
71
71
  ```
72
72
 
73
+ This snippet assumes a **live answered** channel: inbound after you `answer()`, or a campaign
74
+ outbound that started on `'on_answer'` (the default). It is not how you pick up ringing, 1xx, or
75
+ a 4xx. Those are two different outbound stories:
73
76
 
77
+ - **Campaign / `platform.call`** — `defineScript(fn, { outboundCallMode })` decides *when* the
78
+ host starts this session. See [Campaign Outbound And `outboundCallMode`](#campaign-outbound-and-outboundcallmode).
79
+ - **A second leg from an already-running script** — `channel.sip.makeCall()`, then
80
+ `waitForEarly()` / `waitForAnswer()` on the **returned** channel. See
81
+ [B-leg outbound (already in session)](#b-leg-outbound-already-in-session).
74
82
 
75
83
  ## Examples
76
84
 
@@ -79,6 +87,9 @@ The `[examples/](./examples/)` folder contains copy-paste-ready scripts:
79
87
 
80
88
  | Example | Description |
81
89
  | --------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
90
+ | [outbound-on-answer.ts](./examples/outbound-on-answer.ts) | Campaign outbound on `200 OK` (`outboundCallMode: 'on_answer'`, the default) |
91
+ | [outbound-from-invite.ts](./examples/outbound-from-invite.ts) | Campaign outbound from INVITE (`outboundCallMode: 'from_invite'`) |
92
+ | [outbound-script-dial.ts](./examples/outbound-script-dial.ts) | Script dials the campaign leg (`outboundCallMode: 'script_dial'`) |
82
93
  | [outbound-with-recall.ts](./examples/outbound-with-recall.ts) | Outbound with `recallCount` / `recallDelay` (automatic redial on failure) |
83
94
  | [recall-routing-by-attempt.ts](./examples/recall-routing-by-attempt.ts) | Online recall: branch on `context.attempt` (`getScriptPhase` → `'online'`) |
84
95
  | [schedule-call-with-defaults.ts](./examples/schedule-call-with-defaults.ts) | `platform.call()` without explicit recall — CMS defaults from `context` |
@@ -130,6 +141,11 @@ const maxAttempts = parseRecallCount(context.dialogParams?.recall_count);
130
141
  Ship it in a **sibling** module (`media-providers/`), not in the sandboxed script entry — see
131
142
  [Custom ASR / TTS Providers](#custom-asr--tts-providers).
132
143
 
144
+ `defineScript(fn, options)` additionally declares `ScriptCallOptions`, whose `outboundCallMode`
145
+ (`OutboundCallMode`: `'on_answer'` | `'from_invite'` | `'script_dial'`) the host reads
146
+ **before** the script runs to decide when the session starts — see
147
+ [Campaign Outbound And `outboundCallMode`](#campaign-outbound-and-outboundcallmode).
148
+
133
149
  **Three different “context” names:**
134
150
 
135
151
 
@@ -172,6 +188,11 @@ The important states are:
172
188
  - `active`: final 200 OK has been received or sent.
173
189
  - `terminated`: the call ended and no more audio is possible.
174
190
 
191
+ `channel.sip.state === 'early'` is **media** (RTP before `200 OK`). It is not
192
+ `getScriptPhase(context) === 'early'`. That phase is a snapshot of *when the live session started*
193
+ (campaign `'from_invite'` or inbound pre-answer) and **does not flip** when the call answers — use
194
+ `channel.sip.isAnswered` / `answered$` for the live state.
195
+
175
196
 
176
197
 
177
198
  ### How Pre-Answer Works
@@ -196,9 +217,20 @@ For outbound calls, pre-answer is controlled by the remote side. If the remote e
196
217
 
197
218
  `early` is a media-ready state, not a final answer state. `answer()` is still the explicit transition that sends final `200 OK` for inbound calls. External billing behavior depends on the carrier.
198
219
 
199
- ### Outbound Pre-Answer
220
+ ### Two outbound stories
221
+
222
+ Do not mix these up. Both can see 183 / early media, but they start in different places.
223
+
224
+ | You want… | Use | Conversation on |
225
+ | --------- | --- | --------------- |
226
+ | The **campaign** (or `platform.call`) to start this script at 200 OK, at INVITE, or with no leg yet | `defineScript(fn, { outboundCallMode })` — [below](#campaign-outbound-and-outboundcallmode) | `channel`, except `'script_dial'` which talks on the **returned** `leg` |
227
+ | A **second** outbound from a script that is already running (transfer target, IVR, operator) | `channel.sip.makeCall()` — next subsection | the **returned** B-leg |
228
+
229
+ `outboundCallMode` never changes inbound or a B-leg you opened yourself.
230
+
231
+ ### B-leg outbound (already in session)
200
232
 
201
- For outbound calls, early media starts when the remote side sends a provisional response with SDP, usually `183 Session Progress`. This is useful for IVRs that speak before answering.
233
+ Early media on a B-leg starts when the remote side sends a provisional response with SDP, usually `183 Session Progress`. This is useful for IVRs that speak before answering. The parent script is already live; `outboundCallMode` does not apply here.
202
234
 
203
235
  ```ts
204
236
  const bLeg = await channel.sip.makeCall({
@@ -217,6 +249,175 @@ asr.result$.subscribe((text) => {
217
249
 
218
250
  `waitForEarly()` resolves when the call reaches either `early` or `active`. If a carrier skips early media and answers directly, it resolves on the final answer.
219
251
 
252
+ ### Campaign Outbound And `outboundCallMode`
253
+
254
+ This is **when the platform starts the script** for a campaign / `platform.call` row — not how you
255
+ open a B-leg from inside a live session ([that is `makeCall`](#b-leg-outbound-already-in-session)).
256
+
257
+ `OutboundCallMode` (`'on_answer'` | `'from_invite'` | `'script_dial'`) is the second argument of
258
+ `defineScript`. The host reads it **before** the session starts, because it decides *when* (and
259
+ whether) the platform dials. The names describe that moment.
260
+
261
+ ```ts
262
+ import { defineScript } from '@voctiv/agent-sdk';
263
+
264
+ defineScript(async () => undefined); // same as 'on_answer'
265
+ defineScript(async () => undefined, { outboundCallMode: 'on_answer' });
266
+ defineScript(async () => undefined, { outboundCallMode: 'from_invite' });
267
+ defineScript(async () => undefined, { outboundCallMode: 'script_dial' });
268
+ ```
269
+
270
+ A script that passes nothing stays on `'on_answer'` — the Basic Script above is that path.
271
+ Inbound legs and `channel.sip.makeCall()` B-legs are unchanged: they already hand the script a
272
+ channel before the final answer.
273
+
274
+ `answer_date` and billing never move: both come from the `200 OK`, never from `183`.
275
+ `getScriptPhase` is a snapshot of how the run **started** and does not flip when the answer arrives
276
+ — use `channel.sip.isAnswered` or `answered$` for the live state. (`defineScript` stores the
277
+ options on the function as `callOptions` — type `ScriptFnWithCallOptions` — and the host copies
278
+ that into `context.startedBeforeAnswer` / `context.startedWithoutLeg`.)
279
+
280
+
281
+ | `outboundCallMode` | Who dials | Script starts on | `getScriptPhase` at start | Conversation channel |
282
+ | ----------------------- | ----------------------- | ----------------------------------- | ------------------------------------------------------ | ---------------------- |
283
+ | `'on_answer'` (default) | host | the `200 OK` | `'online'` | `channel` |
284
+ | `'from_invite'` | host (after script up) | INVITE — session is already running | `'early'` (stays `'early'` after answer) | `channel` |
285
+ | `'script_dial'` | script (`sip.makeCall`) | nothing — no leg exists yet | `'dialing'` (stays `'dialing'` after the script dials) | the **returned** `leg` |
286
+
287
+ Copy-paste: [outbound-on-answer.ts](./examples/outbound-on-answer.ts),
288
+ [outbound-from-invite.ts](./examples/outbound-from-invite.ts),
289
+ [outbound-script-dial.ts](./examples/outbound-script-dial.ts).
290
+
291
+ #### `'on_answer'` — start on answer (default)
292
+
293
+ The host sends the INVITE and **holds the script** until the remote party answers (`200 OK`). Busy,
294
+ no-answer, reject, or the dialer timeout never reach the live handler: the campaign writes
295
+ `call.result` and may run `after_call_failed` / recall instead.
296
+
297
+ Use this when the script is a conversation: greeting, turn watchdog, max-duration timer. They all
298
+ assume a human is already on the line. Existing agents that do not pass options keep this behavior.
299
+
300
+ ```ts
301
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
302
+
303
+ export default defineScript(async ({ channel, context }) => {
304
+ // Live outbound: always 'online'. Headless before_call / after_call_* still apply.
305
+ if (getScriptPhase(context) !== 'online') return;
306
+
307
+ await channel.audio.say('Hello!');
308
+ const asr = await channel.createAsr();
309
+ });
310
+ ```
311
+
312
+ How to work with it:
313
+
314
+ - Talk on `channel` — audio, ASR, SIP waiters are already live and answered.
315
+ - Do **not** wait for `183` / `progress$`: the script was not running then.
316
+ - Do **not** call `channel.sip.makeCall()` for the campaign number; the host already dialed it.
317
+ - Inbound is unchanged: the same export still sees `'online'` (or `'early'` only if you also opted
318
+ into `'from_invite'`).
319
+
320
+ #### `'from_invite'` — script first, host dials
321
+
322
+ The host starts the script **before** any INVITE, then automatically dials the campaign number on
323
+ the same `channel`. Subscribe first, then wait: 180/183 arrive on `progress$` / `sipSignal$`, and a
324
+ 4xx/5xx rejects `waitForAnswer()` with the SIP result in the message (`486 Busy Here`). The
325
+ greeting still belongs after the answer — otherwise it plays into ringback.
326
+
327
+ ```ts
328
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
329
+
330
+ export default defineScript(
331
+ async ({ channel, logger, context }) => {
332
+ if (getScriptPhase(context) === 'early') {
333
+ channel.sip.progress$.subscribe(({ statusCode }) => {
334
+ logger.log('progress', { statusCode });
335
+ });
336
+ try {
337
+ await channel.sip.waitForAnswer();
338
+ } catch (err) {
339
+ logger.warn('outbound failed', { err });
340
+ return;
341
+ }
342
+ }
343
+
344
+ await channel.audio.say('Hello!');
345
+ },
346
+ { outboundCallMode: 'from_invite' },
347
+ );
348
+ ```
349
+
350
+ How to work with it:
351
+
352
+ - Talk on `channel` — the host already dialed; do **not** call `makeCall()` for the campaign number.
353
+ - `getScriptPhase` is `'early'` for the whole live run (it does not flip on `200 OK`). Read
354
+ `channel.sip.isAnswered` / `answered$` for the live state.
355
+ - `audio.say` / `createAsr` wait until the leg has media, then run on this channel.
356
+ - On inbound, `'from_invite'` still reports `'early'` when the session starts before the answer.
357
+ - Unlike `'on_answer'`, a 4xx/5xx **is** visible in this handler — catch `waitForAnswer()` or subscribe
358
+ to `sipSignal$` / `events.terminated$`.
359
+
360
+ #### `'script_dial'` — dial the leg yourself
361
+
362
+ The host does **not** send an INVITE. The script starts with no leg: `sip.state === 'idle'`, no
363
+ audio, phase `'dialing'`. It opens the leg itself and talks on the channel that **comes back** —
364
+ the same shape as a `makeCall()` B-leg. The starting `channel` can only dial.
365
+
366
+ ```ts
367
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
368
+
369
+ export default defineScript(
370
+ async ({ channel, logger, context }) => {
371
+ if (getScriptPhase(context) !== 'dialing') return;
372
+
373
+ // Empty options: campaign msisdn + the call row's trunk.
374
+ const leg = await channel.sip.makeCall({});
375
+
376
+ leg.sip.progress$.subscribe(({ statusCode }) => {
377
+ logger.log('progress', { statusCode });
378
+ });
379
+ await leg.sip.waitForAnswer();
380
+
381
+ await leg.audio.say('Hello!');
382
+ const asr = await leg.createAsr();
383
+ },
384
+ { outboundCallMode: 'script_dial' },
385
+ );
386
+ ```
387
+
388
+ How to work with it:
389
+
390
+ - **Conversation is on `leg`, not on `channel`.** `channel.audio.say` / `createAsr` before
391
+ `makeCall` are no-ops with a warning. `channel.sip.waitForAnswer()` rejects — there is no leg.
392
+ - `makeCall({})` dials the campaign number over the row's trunk. Pass `msisdn` (and `channel` for a
393
+ different trunk) to dial somewhere else, exactly as in a live session.
394
+ - `makeCall` **resolves on media** (183/200) and **rejects** when the leg fails (`486 Busy Here`) or
395
+ times out. A retry loop is `try` / `catch`, not a race on `terminated$`.
396
+ - The **first** successful `makeCall` is the one the platform reports on: `call.result`,
397
+ `answer_date`, duration, recording, transcription. Later legs are B-legs. A run that never dials
398
+ leaves the row with `-ERR Script dialed no call`.
399
+ - The dialer's answer timeout no longer applies to the campaign row. The worker slot is held from
400
+ the moment the row is claimed until the script returns. Returning hangs up any leg still up.
401
+ - `getScriptPhase` stays `'dialing'` after the script opens a leg. Gate on it to decide *whether*
402
+ to dial; use `leg.sip.isAnswered` for *whether someone picked up*.
403
+
404
+ An existing agent that already talks on `ctx.channel` does not need a rewrite: take the returned
405
+ leg once, then keep the rest of the script as-is.
406
+
407
+ ```ts
408
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
409
+
410
+ export default defineScript(
411
+ async (ctx) => {
412
+ const phase = getScriptPhase(ctx.context);
413
+ const channel =
414
+ phase === 'dialing' ? await ctx.channel.sip.makeCall({}) : ctx.channel;
415
+ await channel.audio.say('Hello!');
416
+ },
417
+ { outboundCallMode: 'script_dial' },
418
+ );
419
+ ```
420
+
220
421
  ### Inbound Pre-Answer
221
422
 
222
423
  For inbound calls, call `channel.sip.sendProgress()` to send `183 Session Progress` with SDP. This enters `early` state and enables full-duplex audio before the final answer.
@@ -867,12 +1068,16 @@ the full surface and IDEs autocomplete correctly:
867
1068
  | Interface | Role |
868
1069
  | --- | --- |
869
1070
  | `ScriptAsrConnector` | Custom STT connector returned by an `asr` factory |
1071
+ | `ScriptAsrConnectorError` | Error payload on `ScriptAsrConnector.error$` |
870
1072
  | `ScriptTtsConnector` | Unified batch + optional streaming TTS connector |
1073
+ | `ScriptTtsSynthesisContext` | Synthesis context passed into a TTS factory (`TtsSynthesisContext`) |
871
1074
  | `MediaConnectorContext` | `id`, flattened `config`, `dialogUuid`, `debug$` passed into factories |
872
1075
  | `MediaProviderShared` | Optional per-dialog object from `createShared` |
1076
+ | `AsrProviderFactory` / `TtsProviderFactory` | Factory functions in `MediaProvidersDefinition` (`asr` / `tts` maps) |
873
1077
 
874
1078
  `defineMediaProviders` is an identity helper for typing. Factories must return objects that
875
- satisfy those interfaces (classes with `implements` recommended).
1079
+ satisfy those interfaces (classes with `implements` recommended). The argument is a
1080
+ `MediaProvidersDefinition`.
876
1081
 
877
1082
  ### Export shape (host module)
878
1083
 
@@ -1286,11 +1491,18 @@ create ASR or want one subscription for the whole channel).
1286
1491
  | `ask(message, options?)` | `Promise<string>` | Single-shot completion (consumes SSE stream). |
1287
1492
  | `stream(message, options?)` | `Observable<LlmStreamChunk>` | Token/chunk stream; use `chunk.content` for TTS. |
1288
1493
  | `extract(options?)` | `Promise<Record<string, any>>` | Structured extraction via Omni extract API. |
1494
+ | `createDialog(options)` | `Promise<DialogInfo>` | Create dialog (`CreateDialogOptions`, optional `payload`) (HTTP). |
1495
+ | `updateDialog(options)` | `Promise<DialogInfo>` | Patch dialog (`UpdateDialogOptions` / `payload`) (HTTP `/meta`). |
1289
1496
  | `makePersistentStream(options?)` | `PersistentLlmStreamHandle` | Long-lived stream for multi-turn chat. |
1290
1497
 
1498
+ `send()` returns a `PersistentLlmSendHandle` (`requestId`, per-turn `stream$`, `abort()`). Several sends may run in parallel on one socket.
1499
+
1500
+ Persistent handle also exposes `createDialog` / `updateDialog` over Socket.IO (`dialog.create` / `dialog.update`).
1501
+
1291
1502
 
1292
1503
  Common `LlmOptions`: `dialogUuid`, `agentUuid`, `role`, `hidden`, `name` (LLM speaker label — **not**
1293
- the TTS credential `name`), `payload`, `debug`, `agentAliasFilter`, `currentAgentAlias`.
1504
+ the TTS credential `name`), `payload`, `history`, `debug`, `agentAliasFilter`, `currentAgentAlias`,
1505
+ `pseudoReasoning` (manual same-turn hint; skips the assigned reasoner LLM), `requestId`.
1294
1506
 
1295
1507
  ```ts
1296
1508
  const answer = await channel.llm.ask('Summarize the user request', {
@@ -1326,18 +1538,46 @@ const fields = await channel.llm.extract({
1326
1538
  });
1327
1539
  ```
1328
1540
 
1329
- Persistent multi-turn stream:
1541
+ Persistent multi-turn stream (one Socket.IO connection, multiplexed by `requestId`):
1330
1542
 
1331
1543
  ```ts
1544
+ // HTTP: create / update dialog payload for prompt templates
1545
+ const created = await channel.llm.createDialog({
1546
+ agentUuid: context.agentUuid,
1547
+ payload: { customerName: 'Alex', language: 'ru' },
1548
+ });
1549
+ await channel.llm.updateDialog({
1550
+ dialogUuid: context.dialogUuid,
1551
+ payload: { ...created.payload, balance: 1200 },
1552
+ });
1553
+
1332
1554
  const chat = channel.llm.makePersistentStream({
1333
1555
  agentUuid: context.agentUuid,
1334
1556
  dialogUuid: context.dialogUuid,
1335
1557
  });
1336
1558
 
1559
+ // Same over the persistent socket
1560
+ await chat.createDialog({
1561
+ agentUuid: context.agentUuid,
1562
+ payload: { customerName: 'Alex' },
1563
+ });
1564
+ await chat.updateDialog({ payload: { customerName: 'Alex', tier: 'gold' } });
1565
+
1337
1566
  chat.stream$.pipe(map((c) => c.content)).subscribe((text) => logger.debug('LLM chunk', { text }));
1338
1567
 
1339
1568
  chat.send('What is my balance?');
1340
1569
  chat.send('And my last payment date?');
1570
+
1571
+ // Parallel agents on the same socket:
1572
+ const realtime = chat.send(userText, {
1573
+ currentAgentAlias: 'fast',
1574
+ pseudoReasoning: 'User wants to cancel. Confirm identity first.',
1575
+ });
1576
+ const deep = chat.send(userText, { currentAgentAlias: 'researcher' });
1577
+ realtime.stream$.subscribe((c) => { /* TTS */ });
1578
+ deep.stream$.subscribe((c) => { /* notes / later TTS */ });
1579
+ // deep.abort(); // cancel only the deep turn
1580
+
1341
1581
  chat.disconnect();
1342
1582
  ```
1343
1583
 
@@ -1375,6 +1615,8 @@ The return value is the `ScriptPhase` union — one of the phases in the table b
1375
1615
  | Phase | `context.headless` | When | Typical `context.entryPoint` |
1376
1616
  | -------------------- | ------------------ | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
1377
1617
  | `before_call` | `true` | Dialog queue / bulk outbound **before** the first platform call (often schedules `platform.call`) | empty, `main`, `default` |
1618
+ | `dialing` | `false` | Live session with **no leg yet** — script opted into `outboundCallMode: 'script_dial'` and must `makeCall()` | any (ignored for phase; snapshot stays `'dialing'`) |
1619
+ | `early` | `false` | Live session that started **before** the `200 OK` (campaign `'from_invite'` or inbound pre-answer). Not `channel.sip.state === 'early'` (RTP). Snapshot stays `'early'` after answer. | any (ignored for phase) |
1378
1620
  | `online` | `false` | Live SIP session (inbound, outbound, **and automatic recall redials**) | any (ignored for phase) |
1379
1621
  | `after_call_success` | `true` | Headless run **after** a successful call | `on_success_call`, `after_call_success`, `on_done_call` |
1380
1622
  | `after_call_failed` | `true` | Headless run **after** failed attempts (when `onFailedCall` was configured) | `on_failed_call`, `after_call_failed` |
@@ -1419,6 +1661,16 @@ export default defineScript(async ({ channel, context, logger, platform }) => {
1419
1661
  logger.log('Inbound message', { text: context.inboundMessage?.payload });
1420
1662
  return { output: { phase } };
1421
1663
 
1664
+ case 'dialing': {
1665
+ const leg = await channel.sip.makeCall({});
1666
+ await leg.sip.waitForAnswer();
1667
+ await leg.audio.say('Hello.');
1668
+ return;
1669
+ }
1670
+
1671
+ case 'early':
1672
+ await channel.sip.waitForAnswer();
1673
+ // fall through to the conversation
1422
1674
  case 'online':
1423
1675
  channel.sip.answer();
1424
1676
  if ((context.attempt ?? 0) > 0) {
@@ -1694,7 +1946,7 @@ These APIs are available and are the intended tools for offline scripts:
1694
1946
  - `platform.messaging.send()` for outbound messages through the configured platform messaging transport.
1695
1947
  - `platform.call()` for scheduling outbound platform-managed calls.
1696
1948
  - `platform.dialog.entryPoint` and `platform.dialog.result` for updating dialog routing and outcome.
1697
- - `channel.llm.ask()`, `channel.llm.stream()`, and `channel.llm.extract()` for Omni LLM operations.
1949
+ - `channel.llm.ask()`, `channel.llm.stream()`, `channel.llm.extract()`, `channel.llm.createDialog()`, and `channel.llm.updateDialog()` for Omni LLM operations.
1698
1950
  - `logger` for structured logs.
1699
1951
 
1700
1952
  Audio and telephony APIs are intentionally inert:
@@ -1,5 +1,6 @@
1
1
  import type { MediaChannel } from './types/media-channel';
2
2
  import type { ScriptLogger } from './types/logger';
3
+ import type { ScriptCallOptions } from './types/call-options';
3
4
  import type { ScriptDialogContext, ScriptResult } from './types/script-context';
4
5
  import type { PlatformApi } from './types/platform';
5
6
  /**
@@ -42,6 +43,15 @@ export interface ScriptContext {
42
43
  * Return {@link ScriptResult} to persist output (and optional error), or void.
43
44
  */
44
45
  export type ScriptFn = (ctx: ScriptContext) => void | ScriptResult | Promise<void | ScriptResult>;
46
+ /**
47
+ * A {@link ScriptFn} carrying the {@link ScriptCallOptions} passed to
48
+ * {@link defineScript}. The host reads them off the default export **before**
49
+ * the script runs, which is why they live on the function rather than in the
50
+ * returned {@link ScriptResult}.
51
+ */
52
+ export type ScriptFnWithCallOptions = ScriptFn & {
53
+ readonly callOptions?: ScriptCallOptions;
54
+ };
45
55
  /**
46
56
  * Mark the default export as a typed script entry point (identity wrapper; no runtime transform).
47
57
  *
@@ -53,7 +63,11 @@ export type ScriptFn = (ctx: ScriptContext) => void | ScriptResult | Promise<voi
53
63
  * - Use **`channel`** for ASR/TTS (see {@link import('./types/asr-handle').AsrConfig.name},
54
64
  * {@link import('./types/mixer').PlayOptions.name} for LE **`key_storage.name`** selection).
55
65
  * - Use **`context.dialogParams`** / **`context.dialogUuid`** for routing; **`platform`** for NLU, calls, messaging.
56
- * @returns The same **`fn`** reference.
66
+ * @param options - Optional {@link ScriptCallOptions}. Unlike everything else in the
67
+ * SDK these are read by the host **before** the script runs — they decide *when*
68
+ * the session starts — so they are attached to the returned function instead of
69
+ * being read from the handler.
70
+ * @returns The same **`fn`** reference, with `options` attached as `callOptions`.
57
71
  *
58
72
  * @example
59
73
  * ```ts
@@ -67,6 +81,21 @@ export type ScriptFn = (ctx: ScriptContext) => void | ScriptResult | Promise<voi
67
81
  * });
68
82
  * });
69
83
  * ```
84
+ *
85
+ * @example Pick up the outbound leg from the INVITE — see {@link ScriptCallOptions.outboundCallMode}
86
+ * ```ts
87
+ * import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
88
+ *
89
+ * export default defineScript(
90
+ * async ({ channel, context }) => {
91
+ * if (getScriptPhase(context) === 'early') {
92
+ * await channel.sip.waitForAnswer();
93
+ * }
94
+ * await channel.audio.say('Hello!');
95
+ * },
96
+ * { outboundCallMode: 'from_invite' },
97
+ * );
98
+ * ```
70
99
  */
71
- export declare function defineScript(fn: ScriptFn): ScriptFn;
100
+ export declare function defineScript(fn: ScriptFn, options?: ScriptCallOptions): ScriptFnWithCallOptions;
72
101
  //# sourceMappingURL=define-script.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-script.d.ts","sourceRoot":"","sources":["../src/define-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,OAAO,EAAE,YAAY,CAAC;IACtB,0FAA0F;IAC1F,MAAM,EAAE,YAAY,CAAC;IACrB,wFAAwF;IACxF,OAAO,EAAE,mBAAmB,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CACrB,GAAG,EAAE,aAAa,KACf,IAAI,GAAG,YAAY,GAAG,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,QAAQ,CAEnD"}
1
+ {"version":3,"file":"define-script.d.ts","sourceRoot":"","sources":["../src/define-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,OAAO,EAAE,YAAY,CAAC;IACtB,0FAA0F;IAC1F,MAAM,EAAE,YAAY,CAAC;IACrB,wFAAwF;IACxF,OAAO,EAAE,mBAAmB,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,EAAE,WAAW,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CACrB,GAAG,EAAE,aAAa,KACf,IAAI,GAAG,YAAY,GAAG,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,QAAQ,GAAG;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;CAC1C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,EACZ,OAAO,CAAC,EAAE,iBAAiB,GAC1B,uBAAuB,CASzB"}
@@ -12,7 +12,11 @@ exports.defineScript = defineScript;
12
12
  * - Use **`channel`** for ASR/TTS (see {@link import('./types/asr-handle').AsrConfig.name},
13
13
  * {@link import('./types/mixer').PlayOptions.name} for LE **`key_storage.name`** selection).
14
14
  * - Use **`context.dialogParams`** / **`context.dialogUuid`** for routing; **`platform`** for NLU, calls, messaging.
15
- * @returns The same **`fn`** reference.
15
+ * @param options - Optional {@link ScriptCallOptions}. Unlike everything else in the
16
+ * SDK these are read by the host **before** the script runs — they decide *when*
17
+ * the session starts — so they are attached to the returned function instead of
18
+ * being read from the handler.
19
+ * @returns The same **`fn`** reference, with `options` attached as `callOptions`.
16
20
  *
17
21
  * @example
18
22
  * ```ts
@@ -26,8 +30,31 @@ exports.defineScript = defineScript;
26
30
  * });
27
31
  * });
28
32
  * ```
33
+ *
34
+ * @example Pick up the outbound leg from the INVITE — see {@link ScriptCallOptions.outboundCallMode}
35
+ * ```ts
36
+ * import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
37
+ *
38
+ * export default defineScript(
39
+ * async ({ channel, context }) => {
40
+ * if (getScriptPhase(context) === 'early') {
41
+ * await channel.sip.waitForAnswer();
42
+ * }
43
+ * await channel.audio.say('Hello!');
44
+ * },
45
+ * { outboundCallMode: 'from_invite' },
46
+ * );
47
+ * ```
29
48
  */
30
- function defineScript(fn) {
31
- return fn;
49
+ function defineScript(fn, options) {
50
+ if (!options)
51
+ return fn;
52
+ // Non-enumerable so the export keeps looking like a plain function to
53
+ // anything that reflects over it (worker bridges, test doubles).
54
+ return Object.defineProperty(fn, 'callOptions', {
55
+ value: { ...options },
56
+ enumerable: false,
57
+ configurable: true,
58
+ });
32
59
  }
33
60
  //# sourceMappingURL=define-script.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-script.js","sourceRoot":"","sources":["../src/define-script.ts"],"names":[],"mappings":";;AA2EA,oCAEC;AA5BD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAgB,YAAY,CAAC,EAAY;IACvC,OAAO,EAAE,CAAC;AACZ,CAAC"}
1
+ {"version":3,"file":"define-script.js","sourceRoot":"","sources":["../src/define-script.ts"],"names":[],"mappings":";;AAyGA,oCAYC;AAzDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,SAAgB,YAAY,CAC1B,EAAY,EACZ,OAA2B;IAE3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,sEAAsE;IACtE,iEAAiE;IACjE,OAAO,MAAM,CAAC,cAAc,CAAC,EAAE,EAAE,aAAa,EAAE;QAC9C,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE;QACrB,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,IAAI;KACnB,CAA4B,CAAC;AAChC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -29,7 +29,8 @@
29
29
  * and live SIP INFO ({@link import('./types/events').SipInfo} on `sipInfo$`). See **`README.md`** → SIP Signalling Metadata.
30
30
  */
31
31
  export { defineScript } from './define-script';
32
- export type { ScriptContext, ScriptFn } from './define-script';
32
+ export type { ScriptContext, ScriptFn, ScriptFnWithCallOptions, } from './define-script';
33
+ export type { ScriptCallOptions, OutboundCallMode, } from './types/call-options';
33
34
  export { defineMediaProviders } from './types/media-providers';
34
35
  export type { MediaProvidersDefinition, MediaConnectorContext, MediaProviderShared, AsrProviderFactory, TtsProviderFactory, ScriptAsrConnector, ScriptAsrConnectorError, ScriptTtsConnector, TtsSynthesisContext as ScriptTtsSynthesisContext, } from './types/media-providers';
35
36
  export type { ScriptDialogContext, ScriptPhase, ScriptRunTime, ScriptResult, PersistedScriptResult, ScriptError, AgentContext, AgentEnvSetOptions, StorageContextApi, } from './types/script-context';
@@ -38,7 +39,7 @@ export type { NluScriptApi, PlatformApi, DialogApi, ScheduleCallOptions, SendMes
38
39
  export type { ScriptLogger } from './types/logger';
39
40
  export { TranscriptionRole } from './types/logger';
40
41
  export type { MediaChannel, ChannelAudio, ChannelEvents, } from './types/media-channel';
41
- export type { ChannelLlm, LlmOptions, LlmStreamChunk, ExtractOptions, PersistentLlmStreamHandle, } from './types/llm';
42
+ export type { ChannelLlm, LlmOptions, LlmStreamChunk, ExtractOptions, PersistentLlmStreamHandle, PersistentLlmSendHandle, CreateDialogOptions, UpdateDialogOptions, DialogInfo, } from './types/llm';
42
43
  export type { ChannelSip, SipState, SipProgressEvent, SipInviteHeaders, ParsedSdpDetails, } from './types/sip';
43
44
  export type { MediaError } from './types/errors';
44
45
  export type { AsrHandle, AsrConfig, AsrVadConfig, AsrSmartTurnConfig } from './types/asr-handle';
@@ -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,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"}
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,EACV,aAAa,EACb,QAAQ,EACR,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAE9B,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,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,UAAU,GACX,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.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;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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAEH,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AAWrB,2DAA+D;AAAtD,uHAAA,oBAAoB,OAAA;AAyB7B,yDAAwD;AAA/C,gHAAA,cAAc,OAAA;AAavB,yCAAmD;AAA1C,2GAAA,iBAAiB,OAAA;AA4C1B,uDAG+B;AAF7B,2HAAA,0BAA0B,OAAA;AAC1B,qHAAA,oBAAoB,OAAA;AAUtB,+CAA2E;AAAlE,gHAAA,gBAAgB,OAAA;AAAE,uHAAA,uBAAuB,OAAA"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * When the host hands a platform-dialed outbound leg to the script.
3
+ *
4
+ * - `'on_answer'` — the host dials and starts the script on the `200 OK`. The script
5
+ * always gets an answered call, which is what a greeting, a turn watchdog and a
6
+ * max-duration timer assume.
7
+ * - `'from_invite'` — the host starts the script **first**, then automatically
8
+ * dials the campaign number on the same `channel`. The session is already
9
+ * running when the INVITE goes out, so `progress$` / `sipSignal$` see 1xx and
10
+ * a 4xx/5xx rejects `waitForAnswer()`. Phase is `'early'`; gate the greeting
11
+ * on `channel.sip.waitForAnswer()`.
12
+ * - `'script_dial'` — the host does **not** dial. The script starts with no leg at all
13
+ * (phase `'dialing'`) and opens one itself:
14
+ *
15
+ * ```ts
16
+ * const leg = await channel.sip.makeCall({ msisdn: context.calledNumber });
17
+ * await leg.sip.waitForAnswer();
18
+ * ```
19
+ *
20
+ * The leg it gets back is the conversation channel — the one the script plays and
21
+ * listens on. The channel the script starts with can only dial: it has no audio.
22
+ * The first leg opened this way is the one the platform reports on (result,
23
+ * `answer_date`, duration, recording), so a script that never dials leaves the
24
+ * call row with no answer.
25
+ *
26
+ * Inbound legs are unaffected — they always start before the answer. `'from_invite'`
27
+ * additionally lets an inbound run report `'early'` instead of `'online'`.
28
+ *
29
+ * `answer_date` and billing never move: both come from the `200 OK`.
30
+ */
31
+ export type OutboundCallMode = 'on_answer' | 'from_invite' | 'script_dial';
32
+ /**
33
+ * Call-handling options a script declares for itself, passed as the second
34
+ * argument of {@link import('../define-script').defineScript}:
35
+ *
36
+ * ```ts
37
+ * export default defineScript(handler, { outboundCallMode: 'from_invite' });
38
+ * ```
39
+ *
40
+ * The host reads them off the default export **before** the session starts,
41
+ * because they decide *when* it starts. A script that passes nothing gets the
42
+ * documented defaults.
43
+ */
44
+ export interface ScriptCallOptions {
45
+ /**
46
+ * Where the script picks up a platform-dialed outbound leg (campaign dialer,
47
+ * `platform.call`). See {@link OutboundCallMode} for what each mode implies.
48
+ *
49
+ * @defaultValue 'on_answer'
50
+ */
51
+ outboundCallMode?: OutboundCallMode;
52
+ }
53
+ //# sourceMappingURL=call-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-options.d.ts","sourceRoot":"","sources":["../../src/types/call-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa,CAAC;AAE3E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=call-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-options.js","sourceRoot":"","sources":["../../src/types/call-options.ts"],"names":[],"mappings":""}
@@ -22,6 +22,58 @@ export interface LlmOptions {
22
22
  debug?: boolean;
23
23
  /** Restrict which agent aliases may handle the request. */
24
24
  agentAliasFilter?: string[];
25
+ /**
26
+ * Manual same-turn pseudo-reasoning hint for the answering agent.
27
+ * When set, Omni skips the assigned reasoner LLM call and injects this text
28
+ * as a hidden reasoning message before the user turn.
29
+ */
30
+ pseudoReasoning?: string;
31
+ /**
32
+ * Multiplex id for persistent WS turns. Generated by `send()` when omitted.
33
+ * Echoed on every `stream.chunk` / `stream.complete` / `stream.error`.
34
+ */
35
+ requestId?: string;
36
+ }
37
+ /** Create an Omni dialog (HTTP or persistent `dialog.create`). */
38
+ export interface CreateDialogOptions {
39
+ /** Omni agent UUID (required). */
40
+ agentUuid: string;
41
+ /** Optional fixed dialog UUID; Omni generates one when omitted. */
42
+ uuid?: string;
43
+ /** Extra dialog prompt appended after agent system prompt. */
44
+ prompt?: string;
45
+ /** Human-readable dialog name. */
46
+ name?: string;
47
+ /** Template variables persisted on the dialog (`dialog.payload`). */
48
+ payload?: Record<string, any>;
49
+ /** Optional seed history. */
50
+ history?: Array<{
51
+ role?: string;
52
+ content: string;
53
+ hidden?: boolean;
54
+ name?: string;
55
+ }>;
56
+ }
57
+ /** Patch Omni dialog fields (HTTP `/meta` or persistent `dialog.update`). */
58
+ export interface UpdateDialogOptions {
59
+ /** Target dialog UUID; defaults to the channel / persistent stream dialog. */
60
+ dialogUuid?: string;
61
+ /** Required when the dialog may need to be created on first update. */
62
+ agentUuid?: string;
63
+ prompt?: string;
64
+ name?: string;
65
+ /** Replaces stored `dialog.payload` when set. */
66
+ payload?: Record<string, any>;
67
+ metadata?: Record<string, any>;
68
+ currentAgentAlias?: string;
69
+ }
70
+ /** Result of {@link ChannelLlm.createDialog} / {@link PersistentLlmStreamHandle.createDialog}. */
71
+ export interface DialogInfo {
72
+ uuid: string;
73
+ agentUuid?: string;
74
+ payload?: Record<string, any> | null;
75
+ prompt?: string | null;
76
+ currentAgentAlias?: string | null;
25
77
  }
26
78
  /** Options for **`channel.llm.extract`**. All fields except `dialogUuid` are forwarded to Omni extract. */
27
79
  export interface ExtractOptions {
@@ -48,14 +100,38 @@ export interface LlmStreamChunk {
48
100
  event: any;
49
101
  toolMessages?: any[];
50
102
  raw: Record<string, any>;
103
+ /** Set on persistent WS chunks so parallel sends on one socket can be demuxed. */
104
+ requestId?: string;
105
+ /** Alias that produced this chunk when the backend includes it. */
106
+ currentAgentAlias?: string;
107
+ }
108
+ /**
109
+ * One in-flight turn on a {@link PersistentLlmStreamHandle}.
110
+ * Several sends may run in parallel on the same Socket.IO connection.
111
+ */
112
+ export interface PersistentLlmSendHandle {
113
+ readonly requestId: string;
114
+ readonly stream$: Observable<LlmStreamChunk>;
115
+ /** Cancel this turn only (`stream.abort`); other in-flight sends keep running. */
116
+ abort(): void;
51
117
  }
52
118
  /**
53
119
  * Long-lived LLM Socket.IO stream — amortizes connection setup across many user turns.
54
120
  */
55
121
  export interface PersistentLlmStreamHandle {
122
+ /** Merged chunks from every in-flight `send` (filter by `chunk.requestId` if needed). */
56
123
  readonly stream$: Observable<LlmStreamChunk>;
57
- /** Send one user/assistant turn over the existing stream; options override defaults for this send only. */
58
- send(message: string, options?: LlmOptions): void;
124
+ /**
125
+ * Send one turn over the existing socket.
126
+ * Returns a per-turn stream so several agents can run in parallel on one connection.
127
+ */
128
+ send(message: string, options?: LlmOptions): PersistentLlmSendHandle;
129
+ /** Create a dialog (and optional payload) over the persistent socket (`dialog.create`). */
130
+ createDialog(options: CreateDialogOptions): Promise<DialogInfo>;
131
+ /** Update dialog fields / payload over the persistent socket (`dialog.update`). */
132
+ updateDialog(options: UpdateDialogOptions): Promise<DialogInfo>;
133
+ /** Cancel one in-flight turn by `requestId` (`stream.abort`). */
134
+ abort(requestId: string): void;
59
135
  /** Close the underlying Socket.IO stream without destroying the handle object. */
60
136
  disconnect(): void;
61
137
  /** Re-open the underlying Socket.IO stream after {@link disconnect}. */
@@ -69,6 +145,10 @@ export interface ChannelLlm {
69
145
  stream(message: string, options?: LlmOptions): Observable<LlmStreamChunk>;
70
146
  /** Structured extraction / JSON-style fill from conversation context via Omni extract. */
71
147
  extract(options?: ExtractOptions): Promise<Record<string, any>>;
148
+ /** Create an Omni dialog with optional payload (HTTP `POST /v1/chat`). */
149
+ createDialog(options: CreateDialogOptions): Promise<DialogInfo>;
150
+ /** Update Omni dialog payload / prompt / alias (HTTP `POST /v1/chat/:uuid/meta`). */
151
+ updateDialog(options: UpdateDialogOptions): Promise<DialogInfo>;
72
152
  /**
73
153
  * Open a persistent Omni chat stream.
74
154
  * @param options - Default **`agentUuid`**, **`dialogUuid`**, etc.; per-**`send`** overrides allowed.
@@ -1 +1 @@
1
- {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/types/llm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,qFAAqF;AACrF,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,2GAA2G;AAC3G,MAAM,WAAW,cAAc;IAC7B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,+CAA+C;AAC/C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,EAAE,GAAG,CAAC;IACX,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,cAAc,CAAC,CAAC;IAC7C,2GAA2G;IAC3G,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAClD,kFAAkF;IAClF,UAAU,IAAI,IAAI,CAAC;IACnB,wEAAwE;IACxE,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,UAAU;IACzB,4GAA4G;IAC5G,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5D,gFAAgF;IAChF,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC1E,0FAA0F;IAC1F,OAAO,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;IAChE;;;OAGG;IACH,oBAAoB,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,yBAAyB,CAAC;CACvE"}
1
+ {"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/types/llm.ts"],"names":[],"mappings":"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"}
@@ -168,6 +168,27 @@ export interface ScriptDialogContext {
168
168
  * {@link import('./platform').DialogApi.entryPoint} (`platform.dialog.entryPoint`).
169
169
  */
170
170
  entryPoint?: string;
171
+ /**
172
+ * True when this live session started **before** the final 200 OK — the leg had
173
+ * media (183 with SDP) but was not answered yet. Snapshot at script start: it
174
+ * describes how the run began and does not flip when the answer arrives; use
175
+ * `channel.sip.isAnswered` / `answered$` for the live state.
176
+ *
177
+ * Set by the host only for scripts that opted in via
178
+ * {@link import('./call-options').ScriptCallOptions.outboundCallMode} `'from_invite'`,
179
+ * and surfaced as {@link ScriptPhase} `'early'` by {@link getScriptPhase}.
180
+ */
181
+ startedBeforeAnswer?: boolean;
182
+ /**
183
+ * True when this live session started with **no leg at all** — the script dials one
184
+ * itself via `channel.sip.makeCall()`. Set only for scripts that opted in via
185
+ * {@link import('./call-options').ScriptCallOptions.outboundCallMode} `'script_dial'`, and
186
+ * surfaced as {@link ScriptPhase} `'dialing'` by {@link getScriptPhase}.
187
+ *
188
+ * Like {@link startedBeforeAnswer}, a snapshot of how the run began: it stays true after
189
+ * the script opens a leg.
190
+ */
191
+ startedWithoutLeg?: boolean;
171
192
  /** Current recall attempt number for this dialog (from `dialog.params.attempt`, starts at 0). */
172
193
  attempt?: number;
173
194
  /**
@@ -231,11 +252,19 @@ export interface ScriptRunTime {
231
252
  * High-level phase of a script execution lifecycle. Useful for branching logic
232
253
  * between pre-call preparation, the live call, and post-call processing.
233
254
  */
234
- export type ScriptPhase = 'before_call' | 'online' | 'after_call_success' | 'after_call_failed' | 'messaging' | 'recall' | 'headless_other';
255
+ export type ScriptPhase = 'before_call' | 'dialing' | 'early' | 'online' | 'after_call_success' | 'after_call_failed' | 'messaging' | 'recall' | 'headless_other';
235
256
  /**
236
257
  * Determines which phase of the dialog lifecycle the script is currently executing in.
237
258
  *
238
259
  * - `before_call` — headless run that typically schedules an outbound call.
260
+ * - `dialing` — live session that started with no leg: the script opens one itself with
261
+ * `channel.sip.makeCall()` and talks on the channel that returns. Only scripts that opt
262
+ * in via {@link import('./call-options').ScriptCallOptions.outboundCallMode} `'script_dial'`
263
+ * ever see it.
264
+ * - `early` — live SIP/media session that started before the final answer. Only scripts
265
+ * that opt in via {@link import('./call-options').ScriptCallOptions.outboundCallMode}
266
+ * `'from_invite'` ever see it; await `channel.sip.waitForAnswer()` to reach the
267
+ * conversation.
239
268
  * - `online` — live SIP/media session (voice call active).
240
269
  * - `after_call_success` — headless run after a successful call completion.
241
270
  * - `after_call_failed` — headless run after call failure (no answer, reject, etc.).
@@ -1 +1 @@
1
- {"version":3,"file":"script-context.d.ts","sourceRoot":"","sources":["../../src/types/script-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,oEAAoE;AACpE,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,CAAC,EAAE;QACJ,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;QACnD,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3E,CACE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;KAClB,CAAC;IACF;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,iFAAiF;AACjF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,IAAI,EAAE,MAAM,EAAE,KACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;OAGG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC;;;;;;;;;OASG;IACH,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;IAExB;;;;;;OAMG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;;;;;;;;;;;;;;OAeG;IACH,IAAI,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;IAE5D,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAErC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE9B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,WAAW,IAAI,MAAM,CAAC;IACtB,4CAA4C;IAC5C,iBAAiB,IAAI,MAAM,CAAC;IAC5B;;;OAGG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GACnB,aAAa,GACb,QAAQ,GACR,oBAAoB,GACpB,mBAAmB,GACnB,WAAW,GACX,QAAQ,GACR,gBAAgB,CAAC;AAarB;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,mBAAmB,GAAG,WAAW,CAcxE;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,uEAAuE;IACvE,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAsB,SAAQ,YAAY;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B"}
1
+ {"version":3,"file":"script-context.d.ts","sourceRoot":"","sources":["../../src/types/script-context.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,14 @@ const RECALL_ENTRY_POINTS = new Set(['on_recall', 'recall']);
15
15
  * Determines which phase of the dialog lifecycle the script is currently executing in.
16
16
  *
17
17
  * - `before_call` — headless run that typically schedules an outbound call.
18
+ * - `dialing` — live session that started with no leg: the script opens one itself with
19
+ * `channel.sip.makeCall()` and talks on the channel that returns. Only scripts that opt
20
+ * in via {@link import('./call-options').ScriptCallOptions.outboundCallMode} `'script_dial'`
21
+ * ever see it.
22
+ * - `early` — live SIP/media session that started before the final answer. Only scripts
23
+ * that opt in via {@link import('./call-options').ScriptCallOptions.outboundCallMode}
24
+ * `'from_invite'` ever see it; await `channel.sip.waitForAnswer()` to reach the
25
+ * conversation.
18
26
  * - `online` — live SIP/media session (voice call active).
19
27
  * - `after_call_success` — headless run after a successful call completion.
20
28
  * - `after_call_failed` — headless run after call failure (no answer, reject, etc.).
@@ -23,8 +31,11 @@ const RECALL_ENTRY_POINTS = new Set(['on_recall', 'recall']);
23
31
  * - `headless_other` — any other headless invocation not matching the above.
24
32
  */
25
33
  function getScriptPhase(context) {
26
- if (!context.headless)
27
- return 'online';
34
+ if (!context.headless) {
35
+ if (context.startedWithoutLeg)
36
+ return 'dialing';
37
+ return context.startedBeforeAnswer ? 'early' : 'online';
38
+ }
28
39
  const ep = context.entryPoint?.trim().toLowerCase() ?? '';
29
40
  if (AFTER_CALL_SUCCESS_ENTRY_POINTS.has(ep))
30
41
  return 'after_call_success';
@@ -1 +1 @@
1
- {"version":3,"file":"script-context.js","sourceRoot":"","sources":["../../src/types/script-context.ts"],"names":[],"mappings":";;AA4RA,wCAcC;AApCD,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IAC9C,iBAAiB;IACjB,oBAAoB;IACpB,cAAc;CACf,CAAC,CAAC;AACH,MAAM,8BAA8B,GAAG,IAAI,GAAG,CAAC;IAC7C,gBAAgB;IAChB,mBAAmB;CACpB,CAAC,CAAC;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE7D;;;;;;;;;;GAUG;AACH,SAAgB,cAAc,CAAC,OAA4B;IACzD,IAAI,CAAC,OAAO,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAEvC,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAE1D,IAAI,+BAA+B,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,oBAAoB,CAAC;IACzE,IAAI,8BAA8B,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACvE,IAAI,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjD,IAAI,EAAE,KAAK,yBAAyB,IAAI,OAAO,CAAC,cAAc;QAC5D,OAAO,WAAW,CAAC;IAErB,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,MAAM,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IAEnE,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"script-context.js","sourceRoot":"","sources":["../../src/types/script-context.ts"],"names":[],"mappings":";;AA2TA,wCAiBC;AA/CD,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IAC9C,iBAAiB;IACjB,oBAAoB;IACpB,cAAc;CACf,CAAC,CAAC;AACH,MAAM,8BAA8B,GAAG,IAAI,GAAG,CAAC;IAC7C,gBAAgB;IAChB,mBAAmB;CACpB,CAAC,CAAC;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE7D;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,cAAc,CAAC,OAA4B;IACzD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,OAAO,CAAC,iBAAiB;YAAE,OAAO,SAAS,CAAC;QAChD,OAAO,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,CAAC;IAED,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAE1D,IAAI,+BAA+B,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,oBAAoB,CAAC;IACzE,IAAI,8BAA8B,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACvE,IAAI,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,QAAQ,CAAC;IACjD,IAAI,EAAE,KAAK,yBAAyB,IAAI,OAAO,CAAC,cAAc;QAC5D,OAAO,WAAW,CAAC;IAErB,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,MAAM,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,aAAa,CAAC;IAEnE,OAAO,gBAAgB,CAAC;AAC1B,CAAC"}
@@ -6,6 +6,9 @@ Each file exports one `defineScript` handler. The host **always** invokes that s
6
6
 
7
7
  | File | Topic |
8
8
  |------|--------|
9
+ | [outbound-on-answer.ts](./outbound-on-answer.ts) | Campaign outbound on `200 OK` (`outboundCallMode: 'on_answer'`, the default) |
10
+ | [outbound-from-invite.ts](./outbound-from-invite.ts) | Campaign outbound from INVITE (`outboundCallMode: 'from_invite'`) |
11
+ | [outbound-script-dial.ts](./outbound-script-dial.ts) | Script dials the campaign leg (`outboundCallMode: 'script_dial'`) |
9
12
  | [outbound-with-recall.ts](./outbound-with-recall.ts) | Outbound with `recallCount` / `recallDelay` |
10
13
  | [recall-routing-by-attempt.ts](./recall-routing-by-attempt.ts) | Branch on `context.attempt` during online recall legs |
11
14
  | [schedule-call-with-defaults.ts](./schedule-call-with-defaults.ts) | `platform.call()` using CMS defaults from `context` |
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Campaign outbound that starts at INVITE (`outboundCallMode: 'from_invite'`).
3
+ *
4
+ * The host raises the script first, then dials the campaign number on the same
5
+ * `channel`. 1xx lands on `progress$`; a 4xx/5xx rejects `waitForAnswer()`.
6
+ * Gate the greeting on the answer so it does not play into ringback.
7
+ *
8
+ * `getScriptPhase` stays `'early'` for the whole live run. Read
9
+ * `channel.sip.isAnswered` for the live state. Billing stays on the 200 OK.
10
+ */
11
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
12
+
13
+ export default defineScript(
14
+ async ({ channel, logger, context }) => {
15
+ if (getScriptPhase(context) === 'early') {
16
+ channel.sip.progress$.subscribe(({ statusCode }) => {
17
+ logger.log('progress', { statusCode });
18
+ });
19
+ try {
20
+ await channel.sip.waitForAnswer();
21
+ } catch (err) {
22
+ logger.warn('outbound failed', { err });
23
+ return;
24
+ }
25
+ }
26
+
27
+ await channel.audio.say('Hello!');
28
+ },
29
+ { outboundCallMode: 'from_invite' },
30
+ );
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Campaign outbound that starts on the 200 OK (`outboundCallMode: 'on_answer'`).
3
+ *
4
+ * This is the default: omit the second argument of `defineScript` and you get
5
+ * the same timing. The host dials first and holds the script until answer.
6
+ * Busy / no-answer / 4xx never reach this handler.
7
+ *
8
+ * Talk on `channel`. Do not `makeCall()` the campaign number — the host
9
+ * already did. Billing stays on the 200 OK.
10
+ */
11
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
12
+
13
+ export default defineScript(async ({ channel, context }) => {
14
+ // Live outbound: always 'online'. Headless before_call / after_call_* still apply.
15
+ if (getScriptPhase(context) !== 'online') return;
16
+
17
+ await channel.audio.say('Hello!');
18
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Campaign outbound where the script opens the leg (`outboundCallMode: 'script_dial'`).
3
+ *
4
+ * The host does not send an INVITE. Phase is `'dialing'`; the starting `channel`
5
+ * can only dial. Conversation is on the channel `makeCall()` returns.
6
+ * `makeCall({})` uses the campaign msisdn + the call row's trunk.
7
+ *
8
+ * The first successful dial is the campaign row (result, `answer_date`, recording).
9
+ */
10
+ import { defineScript, getScriptPhase } from '@voctiv/agent-sdk';
11
+
12
+ export default defineScript(
13
+ async (ctx) => {
14
+ const phase = getScriptPhase(ctx.context);
15
+ const channel =
16
+ phase === 'dialing' ? await ctx.channel.sip.makeCall({}) : ctx.channel;
17
+ await channel.audio.say('Hello!');
18
+ },
19
+ { outboundCallMode: 'script_dial' },
20
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voctiv/agent-sdk",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Voctiv TypeScript agent SDK: defineScript and platform types for the voice/dialog scripting runtime.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "",