@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.
- package/dist/core/hardenedFetch.d.ts +1 -3
- package/dist/index.js +45 -12
- package/dist/testing/index.js +48 -26
- package/package.json +3 -3
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export declare const hardenFetch: (baseFetch?: typeof fetch) => (
|
|
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
|
|
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) =>
|
|
41824
|
-
|
|
41825
|
-
|
|
41826
|
-
|
|
41827
|
-
|
|
41828
|
-
|
|
41829
|
-
|
|
41830
|
-
|
|
41831
|
-
|
|
41832
|
-
}
|
|
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 ?? [];
|
package/dist/testing/index.js
CHANGED
|
@@ -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) =>
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
}
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|