@dialt/sdk 0.23.0 → 0.24.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.24.0
4
+
5
+ - `DialtClient` is now the canonical browser client name; `ConverseClient` remains as a deprecated source-compatible alias.
6
+ - New sessions send `mode.kind: 'dialt'`; legacy `converse` input is still accepted and normalized.
7
+
3
8
  ## 0.23.0
4
9
 
5
10
  - The package is now published as `@dialt/sdk`; `@trelis/converse` is deprecated.
package/README.md CHANGED
@@ -12,7 +12,7 @@ Keep your persistent `ck_…` API key on your backend. Exchange it for a scoped
12
12
  with `POST /api/v1/session-keys`, then connect from a user gesture:
13
13
 
14
14
  ```js
15
- import { ConverseClient } from '@dialt/sdk';
15
+ import { DialtClient } from '@dialt/sdk';
16
16
 
17
17
  const credential = await fetch('/voice/session', {
18
18
  method: 'POST',
@@ -22,11 +22,11 @@ const credential = await fetch('/voice/session', {
22
22
  return response.json();
23
23
  });
24
24
 
25
- const client = new ConverseClient({
25
+ const client = new DialtClient({
26
26
  url: 'wss://dialt.com/ws',
27
27
  sessionId: credential.session_id,
28
28
  apiKey: credential.api_key,
29
- mode: { kind: 'converse' },
29
+ mode: { kind: 'dialt' },
30
30
  });
31
31
 
32
32
  startButton.addEventListener('click', async () => {
@@ -113,7 +113,7 @@ token. To preserve the same bounded resume opportunity across a full page reload
113
113
  opaque state in tab-scoped storage and import it into the replacement client:
114
114
 
115
115
  ```js
116
- const storageKey = 'converse-resume-state';
116
+ const storageKey = 'dialt-resume-state';
117
117
  const clientOptions = {
118
118
  url: 'wss://dialt.com/ws',
119
119
  sessionId: credential.session_id,
@@ -128,7 +128,7 @@ try {
128
128
  sessionStorage.removeItem(storageKey);
129
129
  }
130
130
 
131
- const client = new ConverseClient(clientOptions);
131
+ const client = new DialtClient(clientOptions);
132
132
  if (savedResumeState !== null) {
133
133
  try {
134
134
  client.importResumeState(savedResumeState);
@@ -159,11 +159,11 @@ Text sessions use the ordinary Dialt model, instructions, tools and events witho
159
159
  microphone or audio pipeline:
160
160
 
161
161
  ```js
162
- const textClient = new ConverseClient({
162
+ const textClient = new DialtClient({
163
163
  url: 'wss://dialt.com/ws',
164
164
  sessionId: credential.session_id,
165
165
  apiKey: credential.api_key,
166
- mode: { kind: 'converse', modality: 'text' },
166
+ mode: { kind: 'dialt', modality: 'text' },
167
167
  });
168
168
  await textClient.connect();
169
169
  textClient.sendText('What is the weather like?');
@@ -199,7 +199,7 @@ arrives), `"long"` for agent or batch jobs; leave it out and Dialt learns from o
199
199
 
200
200
  ```js
201
201
  mode: {
202
- kind: "converse",
202
+ kind: "dialt",
203
203
  tools: [{
204
204
  name: "lookup_order",
205
205
  description: "Look up an order by ID.",
@@ -216,7 +216,7 @@ anything the caller should not wait on — declare the tool as a background job:
216
216
 
217
217
  ```js
218
218
  mode: {
219
- kind: "converse",
219
+ kind: "dialt",
220
220
  tools: [{
221
221
  name: "run_task",
222
222
  description: "Run a long coding task and report when done.",
@@ -271,7 +271,7 @@ an explicit user request to cancel pending work; hosts should stop that call pro
271
271
  Experimental: the API is stable, but this transport is newly shipped and still being hardened on
272
272
  real networks; `ws` remains the default and recommended fallback.
273
273
 
274
- Pass `transport: 'webrtc'` to `ConverseClient` to carry the call over WebRTC (UDP) instead of the
274
+ Pass `transport: 'webrtc'` to `DialtClient` to carry the call over WebRTC (UDP) instead of the
275
275
  default WebSocket — more resilient to jitter and packet loss on weak networks. `ws` remains the
276
276
  default. Safari/WebKit falls back to `ws` automatically. See the
277
277
  [browser guide's WebRTC section](https://dialt.com/docs/api/browser/#webrtc).
@@ -284,7 +284,7 @@ Dialt service, its models, or its server-side implementation.
284
284
  ### Ambience: background music and the thinking sound
285
285
 
286
286
  ```js
287
- const client = new ConverseClient({ url, sessionId, apiKey, mode, ambience: 'thinking' });
287
+ const client = new DialtClient({ url, sessionId, apiKey, mode, ambience: 'thinking' });
288
288
  client.setAmbience('continuous'); // switch live; 'off' | 'thinking' | 'continuous'
289
289
  ```
290
290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dialt/sdk",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "type": "module",
5
5
  "description": "Browser SDK for the Dialt realtime voice and text API",
6
6
  "license": "Apache-2.0",
package/src/ambience.js CHANGED
@@ -385,7 +385,7 @@ export class AmbienceBed {
385
385
  // --- controller ------------------------------------------------------------------------------
386
386
 
387
387
  /** Drives one AmbienceBed on the client's player from the session's events. Owned by
388
- * ConverseClient (its `ambience` option); apps interact through `client.setAmbience(mode)`.
388
+ * DialtClient (its `ambience` option); apps interact through `client.setAmbience(mode)`.
389
389
  *
390
390
  * Holds facts only - mode, whether a reply has played this session, whether Dialt is working
391
391
  * and whether the thinking threshold has elapsed - and derives the bed's envelope target from
package/src/index.js CHANGED
@@ -95,7 +95,7 @@ function sendOneShotFrame(kind, frame, { url, WebSocketImpl = globalThis.WebSock
95
95
  }
96
96
 
97
97
  // Post-session feedback (thumbs + optional comment + device/browser tags). The server files it
98
- // next to the session's recording, so pass the ConverseClient's sessionId.
98
+ // next to the session's recording, so pass the DialtClient's sessionId.
99
99
  export function sendFeedback({ url, sessionId, rating, text, device, browser, apiKey,
100
100
  WebSocketImpl, timeoutMs } = {}) {
101
101
  if (!url || !sessionId) return Promise.reject(new Error('url and sessionId are required'));
@@ -125,7 +125,7 @@ export function sendClientError({ url, sessionId, detail, context, apiKey,
125
125
  return sendOneShotFrame('client_error', frame, { url, WebSocketImpl, timeoutMs });
126
126
  }
127
127
 
128
- const CONVERSE_MODE_FIELDS = new Set([
128
+ const DIALT_MODE_FIELDS = new Set([
129
129
  'kind', 'modality', 'voice', 'instructions', 'tools', 'web_search', 'end_call', 'flow', 'greeting',
130
130
  'temperature', 'silence_nudge_s', 'silence_end_s', 'tool_choice', 'background_audio',
131
131
  ]);
@@ -147,17 +147,18 @@ function validatedToolChoice(value) {
147
147
  }
148
148
  const RELAY_MODE_FIELDS = new Set(['kind', 'provider', 'model', 'voice', 'web_search']);
149
149
 
150
- function validatedMode(value = { kind: 'converse' }) {
150
+ function validatedMode(value = { kind: 'dialt' }) {
151
151
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
152
152
  throw new TypeError('mode must be an object');
153
153
  }
154
154
  const mode = { ...value };
155
+ if (mode.kind === 'converse') mode.kind = 'dialt';
155
156
  for (const [key, item] of Object.entries(mode)) {
156
157
  if (item === undefined) delete mode[key];
157
158
  }
158
- const allowed = mode.kind === 'converse' ? CONVERSE_MODE_FIELDS
159
+ const allowed = mode.kind === 'dialt' ? DIALT_MODE_FIELDS
159
160
  : mode.kind === 'relay' ? RELAY_MODE_FIELDS : null;
160
- if (!allowed) throw new TypeError('mode.kind must be converse or relay');
161
+ if (!allowed) throw new TypeError('mode.kind must be dialt or relay');
161
162
  const extra = Object.keys(mode).find((key) => !allowed.has(key));
162
163
  if (extra) throw new TypeError(`unexpected ${mode.kind} mode field: ${extra}`);
163
164
  const optionalString = (key) => {
@@ -173,9 +174,9 @@ function validatedMode(value = { kind: 'converse' }) {
173
174
  optionalString('voice');
174
175
  optionalBoolean('web_search');
175
176
  mode.web_search ??= false;
176
- if (mode.kind === 'converse') {
177
+ if (mode.kind === 'dialt') {
177
178
  if (mode.modality !== undefined && mode.modality !== 'voice' && mode.modality !== 'text') {
178
- throw new TypeError('converse modality must be voice or text');
179
+ throw new TypeError('dialt modality must be voice or text');
179
180
  }
180
181
  mode.modality ??= 'voice';
181
182
  optionalString('instructions');
@@ -185,20 +186,20 @@ function validatedMode(value = { kind: 'converse' }) {
185
186
  optionalBoolean('flow');
186
187
  optionalBoolean('background_audio');
187
188
  if (Object.hasOwn(mode, 'tools') && !Array.isArray(mode.tools)) {
188
- throw new TypeError('converse tools must be an array');
189
+ throw new TypeError('dialt tools must be an array');
189
190
  }
190
191
  if (Object.hasOwn(mode, 'tool_choice')) {
191
192
  if (!Array.isArray(mode.tools) || !mode.tools.length) {
192
- throw new TypeError('converse tool_choice requires a non-empty tools list');
193
+ throw new TypeError('dialt tool_choice requires a non-empty tools list');
193
194
  }
194
195
  mode.tool_choice = validatedToolChoice(mode.tool_choice);
195
196
  }
196
197
  if (mode.greeting != null && mode.greeting !== false && typeof mode.greeting !== 'string') {
197
- throw new TypeError('converse greeting must be a string or false');
198
+ throw new TypeError('dialt greeting must be a string or false');
198
199
  }
199
200
  if (Object.hasOwn(mode, 'temperature') && (typeof mode.temperature !== 'number'
200
201
  || !Number.isFinite(mode.temperature))) {
201
- throw new TypeError('converse temperature must be a finite number');
202
+ throw new TypeError('dialt temperature must be a finite number');
202
203
  }
203
204
  // Per-session override of the broker's two-stage silence policy (env defaults: 10s/20s) — e.g.
204
205
  // a benchmark harness with long simulated-user think-time. Omit either field to keep the
@@ -207,12 +208,12 @@ function validatedMode(value = { kind: 'converse' }) {
207
208
  for (const key of ['silence_nudge_s', 'silence_end_s']) {
208
209
  if (Object.hasOwn(mode, key) && (typeof mode[key] !== 'number'
209
210
  || !Number.isFinite(mode[key]) || mode[key] <= 0)) {
210
- throw new TypeError(`converse ${key} must be a positive finite number`);
211
+ throw new TypeError(`dialt ${key} must be a positive finite number`);
211
212
  }
212
213
  }
213
214
  if (Object.hasOwn(mode, 'silence_nudge_s') && Object.hasOwn(mode, 'silence_end_s')
214
215
  && mode.silence_end_s <= mode.silence_nudge_s) {
215
- throw new TypeError('converse silence_end_s must be greater than silence_nudge_s');
216
+ throw new TypeError('dialt silence_end_s must be greater than silence_nudge_s');
216
217
  }
217
218
  } else {
218
219
  if (typeof mode.provider !== 'string' || !mode.provider.trim()) {
@@ -246,9 +247,9 @@ function connectMarks() {
246
247
  return { marks, mark };
247
248
  }
248
249
 
249
- export class ConverseClient extends EventTarget {
250
+ export class DialtClient extends EventTarget {
250
251
  constructor({ url, sessionId = createSessionId(), player, apiKey,
251
- mode = { kind: 'converse' }, user, timezone, rawAssist = false,
252
+ mode = { kind: 'dialt' }, user, timezone, rawAssist = false,
252
253
  WebSocketImpl = globalThis.WebSocket,
253
254
  echoCancellerFactory = () => new EchoCanceller(),
254
255
  autoReconnect = true, reconnectBaseMs = 500, reconnectMaxMs = 5000,
@@ -277,7 +278,7 @@ export class ConverseClient extends EventTarget {
277
278
  // the engine, not the browser app, since iOS forces every browser onto WebKit. The track
278
279
  // arrives; Web Audio gets silence. Anyone revisiting this should re-run that probe before
279
280
  // writing code, not after.
280
- console.warn('[converse] webrtc transport is not yet supported on WebKit using ws');
281
+ console.warn('[dialt] webrtc transport is not yet supported on WebKit - using ws');
281
282
  transport = 'ws';
282
283
  }
283
284
  this.url = toWebSocketUrl(url);
@@ -298,7 +299,7 @@ export class ConverseClient extends EventTarget {
298
299
  ambienceOpts.mode ??= 'thinking';
299
300
  const ambienceOnWs = transport === 'ws';
300
301
  if (!ambienceOnWs && ambienceGiven && ambienceOpts.mode !== 'off') {
301
- console.warn('[converse] ambience is not available on the webrtc transport (use mode.background_audio); disabled');
302
+ console.warn('[dialt] ambience is not available on the webrtc transport (use mode.background_audio); disabled');
302
303
  }
303
304
  this.ambience = new Ambience({ player: this.player, enabled: ambienceOnWs, ...ambienceOpts });
304
305
  this.apiKey = apiKey || null;
@@ -823,7 +824,7 @@ export class ConverseClient extends EventTarget {
823
824
  // Shared start-frame construction (mode/temperature/greeting/rawAssist) for both transports.
824
825
  _buildStartFrame() {
825
826
  let mode = validatedMode(this._mode);
826
- if (mode.kind === 'converse') {
827
+ if (mode.kind === 'dialt') {
827
828
  if (this._temperature != null) mode.temperature = this._temperature;
828
829
  if (this._noGreeting) mode.greeting = false;
829
830
  if (mode.background_audio && this.transport !== 'webrtc') {
@@ -833,15 +834,15 @@ export class ConverseClient extends EventTarget {
833
834
  // been downgraded for us: WebKit silently falls back to ws (see the constructor), so a
834
835
  // caller who asked for webrtc + background_audio would otherwise lose the whole session
835
836
  // on iOS instead of just the music.
836
- console.warn('[converse] background_audio requires the webrtc transport disabled');
837
+ console.warn('[dialt] background_audio requires the webrtc transport - disabled');
837
838
  delete mode.background_audio;
838
839
  }
839
840
  }
840
841
  mode = validatedMode(mode);
841
- const textModality = mode.kind === 'converse' && mode.modality === 'text';
842
+ const textModality = mode.kind === 'dialt' && mode.modality === 'text';
842
843
  // Voice is the established wire default. Keep it internally for client-side guards, but omit
843
844
  // it from the frame so this SDK remains voice-compatible with pre-text Dialt servers.
844
- if (mode.kind === 'converse' && mode.modality === 'voice') delete mode.modality;
845
+ if (mode.kind === 'dialt' && mode.modality === 'voice') delete mode.modality;
845
846
  const start = {
846
847
  type: 'start',
847
848
  session_id: this.sessionId,
@@ -850,7 +851,7 @@ export class ConverseClient extends EventTarget {
850
851
  if (!textModality) {
851
852
  start.audio = { sr: SAMPLE_RATE, output_encoding: 'pcm16' };
852
853
  } else if (this.transport !== 'ws') {
853
- throw new TypeError('converse text modality requires the ws transport');
854
+ throw new TypeError('dialt text modality requires the ws transport');
854
855
  }
855
856
  if (this.apiKey) start.api_key = this.apiKey;
856
857
  if (this._resumeToken) start.resume_token = this._resumeToken;
@@ -1330,7 +1331,7 @@ export class ConverseClient extends EventTarget {
1330
1331
  try { fn(); } catch (err) {
1331
1332
  if (!this._ambienceWarned) {
1332
1333
  this._ambienceWarned = true;
1333
- console.warn('[converse] ambience error (disabled for this session):', err);
1334
+ console.warn('[dialt] ambience error (disabled for this session):', err);
1334
1335
  }
1335
1336
  try { this.ambience.setMode('off'); } catch { /* already off */ }
1336
1337
  }
@@ -1563,7 +1564,7 @@ export class ConverseClient extends EventTarget {
1563
1564
  * changes nothing; a mid-session `setTools` resets tool_choice to `"auto"`. */
1564
1565
  setToolChoice(toolChoice, { oneShot = false } = {}) {
1565
1566
  const validated = validatedToolChoice(toolChoice);
1566
- if (!oneShot && this._mode.kind === 'converse'
1567
+ if (!oneShot && this._mode.kind === 'dialt'
1567
1568
  && Array.isArray(this._mode.tools) && this._mode.tools.length) {
1568
1569
  // Durable restrictions fold into the replayed mode (like setVoice) so an auto-reconnect
1569
1570
  // re-applies them; a one-shot is turn-scoped and deliberately does not survive. A mode
@@ -1580,7 +1581,7 @@ export class ConverseClient extends EventTarget {
1580
1581
  setVoice(voice) {
1581
1582
  // Relay providers bind their voice when the upstream session is constructed and do not support
1582
1583
  // this control. Dialt reconnects replay the selected voice.
1583
- if (this._mode.kind !== 'converse') return;
1584
+ if (this._mode.kind !== 'dialt') return;
1584
1585
  this._mode = Object.freeze(validatedMode({ ...this._mode, voice }));
1585
1586
  this._sendControl({ type: 'set_voice', voice });
1586
1587
  }
@@ -1796,3 +1797,6 @@ export class ConverseClient extends EventTarget {
1796
1797
  return detail;
1797
1798
  }
1798
1799
  }
1800
+
1801
+ /** @deprecated Use DialtClient. */
1802
+ export const ConverseClient = DialtClient;
package/src/mic.js CHANGED
@@ -25,7 +25,7 @@ export class CaptureAbortedError extends Error {
25
25
  // distorts levels — so an app that hand-rolls getUserMedia({audio:true}) silently degrades the
26
26
  // loop. Frames come out as 16 kHz 512-sample Float32 via an AudioWorklet resampler.
27
27
  // `processing:false` opens the mic fully raw (AEC+NS+AGC off): used for the optional raw ablation
28
- // track, and by ConverseClient.startMic on WebKit where the SDK's own AEC3 cancels instead.
28
+ // track, and by DialtClient.startMic on WebKit where the SDK's own AEC3 cancels instead.
29
29
  export class MicCapture {
30
30
  constructor({ onFrame, processing = true, workletUrl, deviceId } = {}) {
31
31
  this.onFrame = onFrame;
package/src/webrtc.js CHANGED
@@ -1,14 +1,14 @@
1
- // WebRTC transport support for ConverseClient see serving/broker_webrtc.py's module docstring
1
+ // WebRTC transport support for DialtClient - see serving/broker_webrtc.py's module docstring
2
2
  // for the full wire contract this mirrors. Two independent pieces live here:
3
3
  //
4
4
  // TrackFeeder — turns mic frames (whatever startMic()/pushMicFrame() would otherwise ws.send()
5
5
  // as PCM16) into a real outbound MediaStreamTrack, by re-injecting them through an
6
6
  // AudioWorkletNode into a MediaStreamAudioDestinationNode. RTCPeerConnection.
7
7
  // addTrack() needs a live MediaStreamTrack before the offer/ICE-gather/answer
8
- // exchange even happens and that exchange has to complete before ConverseClient
8
+ // exchange even happens - and that exchange has to complete before DialtClient
9
9
  // knows whether the app will call startMic() at all (connect() resolves first) —
10
10
  // so this feeder's track is always what negotiates the offer's audio m-line.
11
- // Once startMic() runs (the normal path: no forced SDK-side AEC), ConverseClient
11
+ // Once startMic() runs (the normal path: no forced SDK-side AEC), DialtClient
12
12
  // replaceTrack()s the sender straight onto the real getUserMedia device track —
13
13
  // zero added JS hops/latency, no renegotiation needed — and this feeder goes idle.
14
14
  // It stays the ACTIVE uplink only for callers with no MicCapture at all (custom
@@ -23,7 +23,7 @@
23
23
  // round-trip, acceptable latency for a single-hop broker-terminated call), and
24
24
  // later applies the server's answer.
25
25
  //
26
- // Both are deliberately dumb/mockable: ConverseClient owns all protocol semantics (start frame,
26
+ // Both are deliberately dumb/mockable: DialtClient owns all protocol semantics (start frame,
27
27
  // ready/bye handling, reconnection policy); this module only knows WebRTC plumbing.
28
28
 
29
29
  import { addWorkletModule, defaultWorkletModuleUrls } from './worklet-url.js';
@@ -157,7 +157,7 @@ export class WebRtcSession {
157
157
  }
158
158
 
159
159
  /** Returns the RTCRtpSender so the caller can later replaceTrack() a real capture device's
160
- * track in directly (see ConverseClient.startMic) without renegotiating. */
160
+ * track in directly (see DialtClient.startMic) without renegotiating. */
161
161
  addAudioTrack(track) {
162
162
  return this.pc.addTrack(track);
163
163
  }