@absolutejs/voice 0.0.22-beta.642 → 0.0.22-beta.644

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.
@@ -1,3 +1 @@
1
- export declare const hardenFetch: (baseFetch?: typeof fetch) => ((input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => Promise<Response>) & {
2
- preconnect: typeof fetch.preconnect;
3
- };
1
+ export declare const hardenFetch: (baseFetch?: typeof fetch) => (input: Parameters<typeof fetch>[0], init: Parameters<typeof fetch>[1]) => Promise<Response>;
package/dist/index.js CHANGED
@@ -4574,6 +4574,8 @@ var createVoiceSession = (options) => {
4574
4574
  user: 0
4575
4575
  };
4576
4576
  const recordingFormats = {};
4577
+ const recordingStartedAt = Date.now();
4578
+ let assistantRecordingEndsAtMs = 0;
4577
4579
  let recordingPersisted = false;
4578
4580
  const captureRecordingChunk = (channel, bytes, format) => {
4579
4581
  if (!recordingConfig || recordingPersisted) {
@@ -4589,11 +4591,33 @@ var createVoiceSession = (options) => {
4589
4591
  if (currentTotal >= recordingMaxBytes) {
4590
4592
  return;
4591
4593
  }
4592
- const remaining = recordingMaxBytes - currentTotal;
4594
+ const bytesPerSample = 2;
4595
+ const bytesPerSecond = format.sampleRateHz * format.channels * bytesPerSample;
4596
+ let chunkStartMs = currentTotal / bytesPerSecond * 1000;
4597
+ if (channel === "assistant" || currentTotal === 0) {
4598
+ const wallClockOffsetMs = Math.max(0, Date.now() - recordingStartedAt);
4599
+ chunkStartMs = channel === "assistant" ? Math.max(wallClockOffsetMs, assistantRecordingEndsAtMs) : wallClockOffsetMs;
4600
+ const targetByteOffset = Math.floor(chunkStartMs * bytesPerSecond / 1000 / bytesPerSample) * bytesPerSample;
4601
+ const silenceByteLength = Math.min(Math.max(0, targetByteOffset - currentTotal), recordingMaxBytes - currentTotal);
4602
+ if (silenceByteLength > 0) {
4603
+ recordingBuffers[channel].push(new Uint8Array(silenceByteLength));
4604
+ recordingByteTotals[channel] += silenceByteLength;
4605
+ }
4606
+ if (channel === "assistant") {
4607
+ assistantRecordingEndsAtMs = chunkStartMs + bytes.byteLength / bytesPerSecond * 1000;
4608
+ }
4609
+ }
4610
+ const updatedTotal = recordingByteTotals[channel];
4611
+ if (updatedTotal >= recordingMaxBytes) {
4612
+ recordingFormats[channel] = format;
4613
+ return;
4614
+ }
4615
+ const remaining = recordingMaxBytes - updatedTotal;
4593
4616
  const slice = bytes.byteLength <= remaining ? bytes : bytes.subarray(0, remaining);
4594
4617
  recordingBuffers[channel].push(new Uint8Array(slice));
4595
4618
  recordingByteTotals[channel] += slice.byteLength;
4596
4619
  recordingFormats[channel] = format;
4620
+ return { byteLength: slice.byteLength, startMs: chunkStartMs };
4597
4621
  };
4598
4622
  const pruneTurnAudio = () => {
4599
4623
  const replayWindowMs = sttFallback?.replayWindowMs ?? DEFAULT_FALLBACK_REPLAY_MS;
@@ -4921,7 +4945,7 @@ var createVoiceSession = (options) => {
4921
4945
  };
4922
4946
  const sendAssistantAudio = async (chunk, input) => {
4923
4947
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
4924
- captureRecordingChunk("assistant", normalizedChunk, input.format);
4948
+ const recordingChunk = captureRecordingChunk("assistant", normalizedChunk, input.format);
4925
4949
  kickCallSilenceWatchdog();
4926
4950
  await send({
4927
4951
  chunkBase64: encodeBase64(normalizedChunk),
@@ -4940,6 +4964,10 @@ var createVoiceSession = (options) => {
4940
4964
  if (activeTTSTurnId) {
4941
4965
  await appendTurnLatencyStage({
4942
4966
  at: input.receivedAt,
4967
+ metadata: recordingChunk ? {
4968
+ recordingByteLength: recordingChunk.byteLength,
4969
+ recordingStartMs: recordingChunk.startMs
4970
+ } : undefined,
4943
4971
  stage: "assistant_audio_received",
4944
4972
  turnId: activeTTSTurnId
4945
4973
  });
@@ -41820,16 +41848,21 @@ var oneAttempt = async (baseFetch, input, init) => {
41820
41848
  callerSignal?.removeEventListener("abort", onCallerAbort);
41821
41849
  }
41822
41850
  };
41823
- var hardenFetch = (baseFetch = globalThis.fetch) => Object.assign(async (input, init) => {
41824
- try {
41825
- return await oneAttempt(baseFetch, input, init);
41826
- } catch (error) {
41827
- if (init?.signal?.aborted)
41828
- throw error;
41829
- console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
41830
- return oneAttempt(baseFetch, input, init);
41831
- }
41832
- }, { preconnect: baseFetch.preconnect.bind(baseFetch) });
41851
+ var hardenFetch = (baseFetch = globalThis.fetch) => {
41852
+ const hardenedFetch = async (input, init) => {
41853
+ try {
41854
+ return await oneAttempt(baseFetch, input, init);
41855
+ } catch (error) {
41856
+ if (init?.signal?.aborted)
41857
+ throw error;
41858
+ console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
41859
+ return oneAttempt(baseFetch, input, init);
41860
+ }
41861
+ };
41862
+ return typeof baseFetch.preconnect === "function" ? Object.assign(hardenedFetch, {
41863
+ preconnect: baseFetch.preconnect.bind(baseFetch)
41864
+ }) : hardenedFetch;
41865
+ };
41833
41866
  // src/core/mcpToolset.ts
41834
41867
  var flattenContent = (result) => {
41835
41868
  const blocks = result.content ?? [];
@@ -328,12 +328,7 @@ var applyVoiceAudioCondition = (fixture, condition) => {
328
328
  for (let index = 0;index < output.length; index += 1)
329
329
  output[index] = clamp(output[index] + random() * Math.sqrt(3) * noiseRms);
330
330
  }
331
- return {
332
- ...fixture,
333
- audio: bytes(output),
334
- id: `${fixture.id}--${condition.id}`,
335
- tags: [...fixture.tags ?? [], "conditioned", condition.id]
336
- };
331
+ return { ...fixture, audio: bytes(output), id: `${fixture.id}--${condition.id}`, tags: [...fixture.tags ?? [], "conditioned", condition.id] };
337
332
  };
338
333
  var buildVoiceAudioMatrix = (fixtures, conditions) => fixtures.flatMap((fixture) => conditions.map((condition) => applyVoiceAudioCondition(fixture, condition)));
339
334
  // src/testing/stt.ts
@@ -4759,16 +4754,21 @@ var oneAttempt = async (baseFetch, input, init) => {
4759
4754
  callerSignal?.removeEventListener("abort", onCallerAbort);
4760
4755
  }
4761
4756
  };
4762
- var hardenFetch = (baseFetch = globalThis.fetch) => Object.assign(async (input, init) => {
4763
- try {
4764
- return await oneAttempt(baseFetch, input, init);
4765
- } catch (error) {
4766
- if (init?.signal?.aborted)
4767
- throw error;
4768
- console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
4769
- return oneAttempt(baseFetch, input, init);
4770
- }
4771
- }, { preconnect: baseFetch.preconnect.bind(baseFetch) });
4757
+ var hardenFetch = (baseFetch = globalThis.fetch) => {
4758
+ const hardenedFetch = async (input, init) => {
4759
+ try {
4760
+ return await oneAttempt(baseFetch, input, init);
4761
+ } catch (error) {
4762
+ if (init?.signal?.aborted)
4763
+ throw error;
4764
+ console.warn(`[voice] hardened fetch retrying on a fresh connection: ${error instanceof Error ? error.message : String(error)}`);
4765
+ return oneAttempt(baseFetch, input, init);
4766
+ }
4767
+ };
4768
+ return typeof baseFetch.preconnect === "function" ? Object.assign(hardenedFetch, {
4769
+ preconnect: baseFetch.preconnect.bind(baseFetch)
4770
+ }) : hardenedFetch;
4771
+ };
4772
4772
 
4773
4773
  // src/core/modelAdapters.ts
4774
4774
  var normalizeVoiceUsage = (provider, model, raw) => {
@@ -6010,15 +6010,9 @@ var stableBenchmarkJson = (value) => {
6010
6010
  };
6011
6011
  var buildVoiceBenchmarkArtifact = (manifest, report) => {
6012
6012
  const payload = { manifest, report };
6013
- return {
6014
- ...payload,
6015
- artifactSha256: sha256Bytes(stableBenchmarkJson(payload))
6016
- };
6013
+ return { ...payload, artifactSha256: sha256Bytes(stableBenchmarkJson(payload)) };
6017
6014
  };
6018
- var verifyVoiceBenchmarkArtifact = (artifact) => artifact.artifactSha256 === sha256Bytes(stableBenchmarkJson({
6019
- manifest: artifact.manifest,
6020
- report: artifact.report
6021
- }));
6015
+ var verifyVoiceBenchmarkArtifact = (artifact) => artifact.artifactSha256 === sha256Bytes(stableBenchmarkJson({ manifest: artifact.manifest, report: artifact.report }));
6022
6016
  // src/core/memoryStore.ts
6023
6017
  var createVoiceMemoryStore = () => {
6024
6018
  const sessions = new Map;
@@ -7217,6 +7211,8 @@ var createVoiceSession = (options) => {
7217
7211
  user: 0
7218
7212
  };
7219
7213
  const recordingFormats = {};
7214
+ const recordingStartedAt = Date.now();
7215
+ let assistantRecordingEndsAtMs = 0;
7220
7216
  let recordingPersisted = false;
7221
7217
  const captureRecordingChunk = (channel, bytes2, format) => {
7222
7218
  if (!recordingConfig || recordingPersisted) {
@@ -7232,11 +7228,33 @@ var createVoiceSession = (options) => {
7232
7228
  if (currentTotal >= recordingMaxBytes) {
7233
7229
  return;
7234
7230
  }
7235
- const remaining = recordingMaxBytes - currentTotal;
7231
+ const bytesPerSample = 2;
7232
+ const bytesPerSecond = format.sampleRateHz * format.channels * bytesPerSample;
7233
+ let chunkStartMs = currentTotal / bytesPerSecond * 1000;
7234
+ if (channel === "assistant" || currentTotal === 0) {
7235
+ const wallClockOffsetMs = Math.max(0, Date.now() - recordingStartedAt);
7236
+ chunkStartMs = channel === "assistant" ? Math.max(wallClockOffsetMs, assistantRecordingEndsAtMs) : wallClockOffsetMs;
7237
+ const targetByteOffset = Math.floor(chunkStartMs * bytesPerSecond / 1000 / bytesPerSample) * bytesPerSample;
7238
+ const silenceByteLength = Math.min(Math.max(0, targetByteOffset - currentTotal), recordingMaxBytes - currentTotal);
7239
+ if (silenceByteLength > 0) {
7240
+ recordingBuffers[channel].push(new Uint8Array(silenceByteLength));
7241
+ recordingByteTotals[channel] += silenceByteLength;
7242
+ }
7243
+ if (channel === "assistant") {
7244
+ assistantRecordingEndsAtMs = chunkStartMs + bytes2.byteLength / bytesPerSecond * 1000;
7245
+ }
7246
+ }
7247
+ const updatedTotal = recordingByteTotals[channel];
7248
+ if (updatedTotal >= recordingMaxBytes) {
7249
+ recordingFormats[channel] = format;
7250
+ return;
7251
+ }
7252
+ const remaining = recordingMaxBytes - updatedTotal;
7236
7253
  const slice = bytes2.byteLength <= remaining ? bytes2 : bytes2.subarray(0, remaining);
7237
7254
  recordingBuffers[channel].push(new Uint8Array(slice));
7238
7255
  recordingByteTotals[channel] += slice.byteLength;
7239
7256
  recordingFormats[channel] = format;
7257
+ return { byteLength: slice.byteLength, startMs: chunkStartMs };
7240
7258
  };
7241
7259
  const pruneTurnAudio = () => {
7242
7260
  const replayWindowMs = sttFallback?.replayWindowMs ?? DEFAULT_FALLBACK_REPLAY_MS;
@@ -7564,7 +7582,7 @@ var createVoiceSession = (options) => {
7564
7582
  };
7565
7583
  const sendAssistantAudio = async (chunk, input) => {
7566
7584
  const normalizedChunk = chunk instanceof Uint8Array ? new Uint8Array(chunk) : chunk instanceof ArrayBuffer ? new Uint8Array(chunk.slice(0)) : new Uint8Array(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength));
7567
- captureRecordingChunk("assistant", normalizedChunk, input.format);
7585
+ const recordingChunk = captureRecordingChunk("assistant", normalizedChunk, input.format);
7568
7586
  kickCallSilenceWatchdog();
7569
7587
  await send({
7570
7588
  chunkBase64: encodeBase64(normalizedChunk),
@@ -7583,6 +7601,10 @@ var createVoiceSession = (options) => {
7583
7601
  if (activeTTSTurnId) {
7584
7602
  await appendTurnLatencyStage({
7585
7603
  at: input.receivedAt,
7604
+ metadata: recordingChunk ? {
7605
+ recordingByteLength: recordingChunk.byteLength,
7606
+ recordingStartMs: recordingChunk.startMs
7607
+ } : undefined,
7586
7608
  stage: "assistant_audio_received",
7587
7609
  turnId: activeTTSTurnId
7588
7610
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.642",
3
+ "version": "0.0.22-beta.644",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -117,7 +117,7 @@
117
117
  "@absolutejs/absolute": ">=0.19.0-beta.646",
118
118
  "@absolutejs/ai": ">=0.0.5",
119
119
  "@angular/core": ">=21.0.0",
120
- "drizzle-orm": ">=1.0.0-rc.1",
120
+ "drizzle-orm": ">=1.0.0-rc.4 <2",
121
121
  "elysia": ">=1.4.18",
122
122
  "react": ">=19.0.0",
123
123
  "vue": ">=3.5.0"
@@ -154,7 +154,7 @@
154
154
  "@types/bun": "1.3.9",
155
155
  "@types/react": "19.2.0",
156
156
  "@typescript-eslint/parser": "^8.57.2",
157
- "drizzle-orm": "1.0.0-rc.3",
157
+ "drizzle-orm": "1.0.0-rc.4",
158
158
  "elysia": "1.4.18",
159
159
  "eslint": "^10.1.0",
160
160
  "eslint-plugin-absolute": "0.11.0-beta.3",