@dtelecom/agents-js 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -121,9 +121,9 @@ declare class Pipeline extends EventEmitter {
121
121
  /** Queued turn while current one is still processing */
122
122
  private pendingTurn;
123
123
  constructor(options: PipelineOptions);
124
- /** One-shot warmup — safe to call from constructor, resolves when both LLM and TTS are ready. */
125
- private _warmupPromise;
126
- private warmup;
124
+ private readonly _warmupPromise;
125
+ private readonly _ttsWarmupPromise;
126
+ private readonly _llmWarmupPromise;
127
127
  get processing(): boolean;
128
128
  get running(): boolean;
129
129
  get agentState(): AgentState;
package/dist/index.d.ts CHANGED
@@ -121,9 +121,9 @@ declare class Pipeline extends EventEmitter {
121
121
  /** Queued turn while current one is still processing */
122
122
  private pendingTurn;
123
123
  constructor(options: PipelineOptions);
124
- /** One-shot warmup — safe to call from constructor, resolves when both LLM and TTS are ready. */
125
- private _warmupPromise;
126
- private warmup;
124
+ private readonly _warmupPromise;
125
+ private readonly _ttsWarmupPromise;
126
+ private readonly _llmWarmupPromise;
127
127
  get processing(): boolean;
128
128
  get running(): boolean;
129
129
  get agentState(): AgentState;
package/dist/index.js CHANGED
@@ -1151,6 +1151,49 @@ var AUDIO_DRAIN_MS = 800;
1151
1151
  function sleep2(ms) {
1152
1152
  return new Promise((resolve) => setTimeout(resolve, ms));
1153
1153
  }
1154
+ function prefetchTTS(tts, text, signal) {
1155
+ const buffer = [];
1156
+ let done = false;
1157
+ let error = null;
1158
+ let wake = null;
1159
+ const notify = () => {
1160
+ if (wake) {
1161
+ const w = wake;
1162
+ wake = null;
1163
+ w();
1164
+ }
1165
+ };
1166
+ void (async () => {
1167
+ try {
1168
+ const stream = tts.synthesize(text, signal);
1169
+ for await (const chunk of stream) {
1170
+ if (signal?.aborted) break;
1171
+ buffer.push(chunk);
1172
+ notify();
1173
+ }
1174
+ } catch (e) {
1175
+ if (!(e instanceof Error && e.name === "AbortError")) error = e;
1176
+ } finally {
1177
+ done = true;
1178
+ notify();
1179
+ }
1180
+ })();
1181
+ return async function* () {
1182
+ let index = 0;
1183
+ while (true) {
1184
+ if (signal?.aborted) return;
1185
+ if (error) throw error;
1186
+ if (index < buffer.length) {
1187
+ yield buffer[index++];
1188
+ continue;
1189
+ }
1190
+ if (done) return;
1191
+ await new Promise((r) => {
1192
+ wake = r;
1193
+ });
1194
+ }
1195
+ };
1196
+ }
1154
1197
  var Pipeline = class extends import_events.EventEmitter {
1155
1198
  stt;
1156
1199
  llm;
@@ -1203,28 +1246,18 @@ var Pipeline = class extends import_events.EventEmitter {
1203
1246
  this.splitter.reset();
1204
1247
  this.setAgentState("idle");
1205
1248
  };
1206
- this._warmupPromise = this.warmup(options.instructions);
1249
+ this._ttsWarmupPromise = this.tts?.warmup ? this.tts.warmup().catch((err) => {
1250
+ log7.warn("TTS warmup failed (non-fatal):", err);
1251
+ }) : Promise.resolve();
1252
+ this._llmWarmupPromise = this.llm.warmup ? this.llm.warmup(options.instructions).catch((err) => {
1253
+ log7.warn("LLM warmup failed (non-fatal):", err);
1254
+ }) : Promise.resolve();
1255
+ this._warmupPromise = Promise.all([this._ttsWarmupPromise, this._llmWarmupPromise]).then(() => {
1256
+ });
1207
1257
  }
1208
- /** One-shot warmup — safe to call from constructor, resolves when both LLM and TTS are ready. */
1209
1258
  _warmupPromise;
1210
- async warmup(instructions) {
1211
- const tasks = [];
1212
- if (this.llm.warmup) {
1213
- tasks.push(
1214
- this.llm.warmup(instructions).catch((err) => {
1215
- log7.warn("LLM warmup failed:", err);
1216
- })
1217
- );
1218
- }
1219
- if (this.tts?.warmup) {
1220
- tasks.push(
1221
- this.tts.warmup().catch((err) => {
1222
- log7.warn("TTS warmup failed:", err);
1223
- })
1224
- );
1225
- }
1226
- await Promise.all(tasks);
1227
- }
1259
+ _ttsWarmupPromise;
1260
+ _llmWarmupPromise;
1228
1261
  get processing() {
1229
1262
  return this._processing;
1230
1263
  }
@@ -1451,29 +1484,50 @@ var Pipeline = class extends import_events.EventEmitter {
1451
1484
  };
1452
1485
  const consumer = async () => {
1453
1486
  this.audioOutput.beginResponse();
1487
+ const state = { prefetched: null };
1454
1488
  try {
1455
1489
  while (true) {
1456
1490
  if (signal.aborted) break;
1457
- if (sentenceQueue.length > 0) {
1458
- const sentence = sentenceQueue.shift();
1491
+ let sentence;
1492
+ let existingStream;
1493
+ if (state.prefetched) {
1494
+ sentence = state.prefetched.sentence;
1495
+ existingStream = state.prefetched.streamFn();
1496
+ state.prefetched = null;
1497
+ } else if (sentenceQueue.length > 0) {
1498
+ sentence = sentenceQueue.shift();
1459
1499
  if (!/\w/.test(sentence)) {
1460
1500
  log7.debug(`Skipping non-word sentence: "${sentence}"`);
1461
1501
  continue;
1462
1502
  }
1463
- await this.synthesizeAndPlay(sentence, signal, (t) => {
1464
- if (!tFirstAudioPlayed) {
1465
- tFirstAudioPlayed = t;
1466
- this.setAgentState("speaking");
1467
- }
1468
- this.emit("sentence", this.cleanText(sentence), sentence);
1503
+ existingStream = void 0;
1504
+ } else if (producerDone) {
1505
+ break;
1506
+ } else {
1507
+ await new Promise((resolve) => {
1508
+ wakeConsumer = resolve;
1469
1509
  });
1510
+ wakeConsumer = null;
1470
1511
  continue;
1471
1512
  }
1472
- if (producerDone) break;
1473
- await new Promise((resolve) => {
1474
- wakeConsumer = resolve;
1475
- });
1476
- wakeConsumer = null;
1513
+ const tryPrefetch = () => {
1514
+ if (state.prefetched || !this.tts) return;
1515
+ if (sentenceQueue.length > 0) {
1516
+ const next = sentenceQueue.shift();
1517
+ if (/\w/.test(next)) {
1518
+ state.prefetched = { sentence: next, streamFn: prefetchTTS(this.tts, next, signal) };
1519
+ }
1520
+ }
1521
+ };
1522
+ tryPrefetch();
1523
+ await this.synthesizeAndPlay(sentence, signal, (t) => {
1524
+ if (!tFirstAudioPlayed) {
1525
+ tFirstAudioPlayed = t;
1526
+ this.setAgentState("speaking");
1527
+ }
1528
+ this.emit("sentence", this.cleanText(sentence), sentence);
1529
+ tryPrefetch();
1530
+ }, existingStream);
1477
1531
  }
1478
1532
  } finally {
1479
1533
  if (!signal.aborted) {
@@ -1526,7 +1580,7 @@ var Pipeline = class extends import_events.EventEmitter {
1526
1580
  return;
1527
1581
  }
1528
1582
  this._processing = true;
1529
- await this._warmupPromise;
1583
+ await this._ttsWarmupPromise;
1530
1584
  log7.info(`say(): "${text.slice(0, 60)}"`);
1531
1585
  try {
1532
1586
  const signal = this.bargeIn.startCycle();
@@ -1563,7 +1617,7 @@ var Pipeline = class extends import_events.EventEmitter {
1563
1617
  }
1564
1618
  }
1565
1619
  }
1566
- async synthesizeAndPlay(text, signal, onFirstAudio) {
1620
+ async synthesizeAndPlay(text, signal, onFirstAudio, existingStream) {
1567
1621
  if (!this.tts || signal.aborted) {
1568
1622
  log7.info(`[Agent says]: ${text}`);
1569
1623
  return;
@@ -1572,7 +1626,7 @@ var Pipeline = class extends import_events.EventEmitter {
1572
1626
  const ttsStart = performance.now();
1573
1627
  let firstChunk = true;
1574
1628
  let ttsChunkCount = 0;
1575
- const ttsStream = this.tts.synthesize(text, signal);
1629
+ const ttsStream = existingStream ?? this.tts.synthesize(text, signal);
1576
1630
  const measuredStream = async function* () {
1577
1631
  for await (const chunk of ttsStream) {
1578
1632
  ttsChunkCount++;