@absolutejs/voice 0.0.22-beta.608 → 0.0.22-beta.609

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.
@@ -63,6 +63,7 @@ export type VoiceAgentModelInput<TContext = unknown, TSession extends VoiceSessi
63
63
  onTextDelta?: (delta: string) => void;
64
64
  session: TSession;
65
65
  system?: string;
66
+ signal?: AbortSignal;
66
67
  tools: Array<{
67
68
  description?: string;
68
69
  name: string;
@@ -148,6 +149,7 @@ export type VoiceAgent<TContext = unknown, TSession extends VoiceSessionRecord =
148
149
  session: TSession;
149
150
  system?: string;
150
151
  turn: VoiceTurnRecord;
152
+ signal?: AbortSignal;
151
153
  }) => Promise<{
152
154
  text: string;
153
155
  } | null>;
@@ -631,6 +631,7 @@ export type VoiceRouteConfig<TContext = unknown, TSession extends VoiceSessionRe
631
631
  context: TContext;
632
632
  session: TSession;
633
633
  turn: VoiceTurnRecord;
634
+ signal?: AbortSignal;
634
635
  }) => Promise<{
635
636
  text: string;
636
637
  } | null | void>;
package/dist/index.js CHANGED
@@ -3851,6 +3851,7 @@ var MAX_TTS_CHUNK_CHARS = 320;
3851
3851
  var STREAM_SENTENCE_END = /[.!?\u2026]['")\]]*$/;
3852
3852
  var STREAM_IDLE_FLUSH_MS = 350;
3853
3853
  var SPECULATIVE_DELAY_MS = 500;
3854
+ var SPECULATION_ADOPT_TIMEOUT_MS = 6000;
3854
3855
  var BACKCHANNEL_DROP_WINDOW_MS = 2000;
3855
3856
  var nextSpeakableBoundary = (buffer) => {
3856
3857
  const match = STREAM_SENTENCE_BOUNDARY.exec(buffer);
@@ -4572,6 +4573,7 @@ var createVoiceSession = (options) => {
4572
4573
  }, delayMs);
4573
4574
  };
4574
4575
  const clearSpeculation = () => {
4576
+ speculation?.controller.abort();
4575
4577
  speculation = null;
4576
4578
  speculationAttempted = false;
4577
4579
  };
@@ -4594,16 +4596,18 @@ var createVoiceSession = (options) => {
4594
4596
  transcripts: session.currentTurn.transcripts
4595
4597
  };
4596
4598
  const speculate = options.route.speculate;
4599
+ const controller = new AbortController;
4597
4600
  const promise = Promise.resolve(speculate({
4598
4601
  api,
4599
4602
  context: options.context,
4600
4603
  session,
4601
- turn: provisionalTurn
4604
+ turn: provisionalTurn,
4605
+ signal: controller.signal
4602
4606
  })).then((result) => result && result.text.trim() ? { text: result.text } : null).catch((error) => {
4603
4607
  console.info(`[voice][p3] speculate error session=${session.id}: ${error instanceof Error ? error.message : String(error)}`);
4604
4608
  return null;
4605
4609
  });
4606
- speculation = { pendingText, promise };
4610
+ speculation = { controller, pendingText, promise };
4607
4611
  };
4608
4612
  const scheduleSilenceCommit = (delayMs = adaptiveSilenceMs(), reset = true) => {
4609
4613
  scheduleTurnCommit(delayMs, "silence", reset);
@@ -5807,18 +5811,26 @@ var createVoiceSession = (options) => {
5807
5811
  const onTurnStartedAt = Date.now();
5808
5812
  logVoiceTiming(session.id, "session.commit-to-onturn", onTurnStartedAt - (turn.committedAt || onTurnStartedAt), { fillerScheduled: fillerTimer !== null });
5809
5813
  const pendingSpeculation = speculation;
5810
- clearSpeculation();
5814
+ speculation = null;
5815
+ speculationAttempted = false;
5811
5816
  let reusableSpeculation;
5812
5817
  if (pendingSpeculation && pendingSpeculation.pendingText === turn.text) {
5813
- const speculated = await pendingSpeculation.promise;
5818
+ const speculated = await Promise.race([
5819
+ pendingSpeculation.promise,
5820
+ new Promise((resolve) => {
5821
+ setTimeout(() => resolve(null), SPECULATION_ADOPT_TIMEOUT_MS);
5822
+ })
5823
+ ]);
5814
5824
  if (speculated?.text) {
5815
5825
  reusableSpeculation = { text: speculated.text };
5816
5826
  logVoiceTiming(session.id, "p3.adopted-speculation", 0, {
5817
5827
  chars: speculated.text.length
5818
5828
  });
5829
+ } else {
5830
+ pendingSpeculation.controller.abort();
5819
5831
  }
5820
- } else {
5821
- pendingSpeculation?.promise;
5832
+ } else if (pendingSpeculation) {
5833
+ pendingSpeculation.controller.abort();
5822
5834
  }
5823
5835
  try {
5824
5836
  const onTurnPromise = options.route.onTurn({
@@ -8103,6 +8115,7 @@ var createVoiceAgent = (options) => {
8103
8115
  context: input.context,
8104
8116
  messages,
8105
8117
  session: input.session,
8118
+ signal: input.signal,
8106
8119
  system,
8107
8120
  tools: [...LIFECYCLE_TOOLS, ...toolMap.values()].map((tool) => ({
8108
8121
  description: tool.description,
@@ -41372,7 +41385,7 @@ var createAIVoiceModel = (options) => ({
41372
41385
  const stream = options.provider.stream({
41373
41386
  messages: toProviderMessages(input.messages),
41374
41387
  model: options.model,
41375
- signal: options.signal,
41388
+ signal: input.signal ?? options.signal,
41376
41389
  systemPrompt,
41377
41390
  tools: toProviderTools(input.tools)
41378
41391
  });
@@ -6078,6 +6078,7 @@ var MAX_TTS_CHUNK_CHARS = 320;
6078
6078
  var STREAM_SENTENCE_END = /[.!?\u2026]['")\]]*$/;
6079
6079
  var STREAM_IDLE_FLUSH_MS = 350;
6080
6080
  var SPECULATIVE_DELAY_MS = 500;
6081
+ var SPECULATION_ADOPT_TIMEOUT_MS = 6000;
6081
6082
  var BACKCHANNEL_DROP_WINDOW_MS = 2000;
6082
6083
  var nextSpeakableBoundary = (buffer) => {
6083
6084
  const match = STREAM_SENTENCE_BOUNDARY.exec(buffer);
@@ -6799,6 +6800,7 @@ var createVoiceSession = (options) => {
6799
6800
  }, delayMs);
6800
6801
  };
6801
6802
  const clearSpeculation = () => {
6803
+ speculation?.controller.abort();
6802
6804
  speculation = null;
6803
6805
  speculationAttempted = false;
6804
6806
  };
@@ -6821,16 +6823,18 @@ var createVoiceSession = (options) => {
6821
6823
  transcripts: session.currentTurn.transcripts
6822
6824
  };
6823
6825
  const speculate = options.route.speculate;
6826
+ const controller = new AbortController;
6824
6827
  const promise = Promise.resolve(speculate({
6825
6828
  api,
6826
6829
  context: options.context,
6827
6830
  session,
6828
- turn: provisionalTurn
6831
+ turn: provisionalTurn,
6832
+ signal: controller.signal
6829
6833
  })).then((result) => result && result.text.trim() ? { text: result.text } : null).catch((error) => {
6830
6834
  console.info(`[voice][p3] speculate error session=${session.id}: ${error instanceof Error ? error.message : String(error)}`);
6831
6835
  return null;
6832
6836
  });
6833
- speculation = { pendingText, promise };
6837
+ speculation = { controller, pendingText, promise };
6834
6838
  };
6835
6839
  const scheduleSilenceCommit = (delayMs = adaptiveSilenceMs(), reset = true) => {
6836
6840
  scheduleTurnCommit(delayMs, "silence", reset);
@@ -8034,18 +8038,26 @@ var createVoiceSession = (options) => {
8034
8038
  const onTurnStartedAt = Date.now();
8035
8039
  logVoiceTiming(session.id, "session.commit-to-onturn", onTurnStartedAt - (turn.committedAt || onTurnStartedAt), { fillerScheduled: fillerTimer !== null });
8036
8040
  const pendingSpeculation = speculation;
8037
- clearSpeculation();
8041
+ speculation = null;
8042
+ speculationAttempted = false;
8038
8043
  let reusableSpeculation;
8039
8044
  if (pendingSpeculation && pendingSpeculation.pendingText === turn.text) {
8040
- const speculated = await pendingSpeculation.promise;
8045
+ const speculated = await Promise.race([
8046
+ pendingSpeculation.promise,
8047
+ new Promise((resolve2) => {
8048
+ setTimeout(() => resolve2(null), SPECULATION_ADOPT_TIMEOUT_MS);
8049
+ })
8050
+ ]);
8041
8051
  if (speculated?.text) {
8042
8052
  reusableSpeculation = { text: speculated.text };
8043
8053
  logVoiceTiming(session.id, "p3.adopted-speculation", 0, {
8044
8054
  chars: speculated.text.length
8045
8055
  });
8056
+ } else {
8057
+ pendingSpeculation.controller.abort();
8046
8058
  }
8047
- } else {
8048
- pendingSpeculation?.promise;
8059
+ } else if (pendingSpeculation) {
8060
+ pendingSpeculation.controller.abort();
8049
8061
  }
8050
8062
  try {
8051
8063
  const onTurnPromise = options.route.onTurn({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.608",
3
+ "version": "0.0.22-beta.609",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",