@absolutejs/voice 0.0.22-beta.475 → 0.0.22-beta.476

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.
@@ -0,0 +1,10 @@
1
+ import type { AIProviderConfig } from "@absolutejs/ai";
2
+ import type { VoiceAgentModel } from "./agent";
3
+ import type { VoiceSessionRecord } from "./types";
4
+ export type CreateAIVoiceModelOptions = {
5
+ model: string;
6
+ provider: AIProviderConfig;
7
+ signal?: AbortSignal;
8
+ systemPrompt?: string;
9
+ };
10
+ export declare const createAIVoiceModel: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(options: CreateAIVoiceModelOptions) => VoiceAgentModel<TContext, TSession, TResult>;
package/dist/index.d.ts CHANGED
@@ -69,6 +69,8 @@ export { assertVoiceSimulationSuiteEvidence, createVoiceSimulationSuiteRoutes, e
69
69
  export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult, } from "./workflowContract";
70
70
  export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, createVoiceSessionReplayJSONHandler, createVoiceSessionReplayRoutes, createVoiceSessionsHTMLHandler, createVoiceSessionsJSONHandler, renderVoiceSessionsHTML, summarizeVoiceProviderFallbackRecovery, summarizeVoiceSessions, summarizeVoiceSessionReplay, } from "./sessionReplay";
71
71
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
72
+ export { createAIVoiceModel } from "./aiVoiceModel";
73
+ export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
72
74
  export { createVoiceRAGTool } from "./ragTool";
73
75
  export type { VoiceRAGCollectionLike, VoiceRAGQueryResult, VoiceRAGSearchInput, VoiceRAGToolArgs, VoiceRAGToolOptions, VoiceRAGToolResult, } from "./ragTool";
74
76
  export { createVoiceApiRequestTool, createVoiceDTMFTool, createVoiceEndCallTool, createVoiceTransferCallTool, createVoiceVoicemailDetectionTool, } from "./agentTools";
package/dist/index.js CHANGED
@@ -3369,6 +3369,9 @@ var buildTurnText = (transcripts, partialText, options = {}) => {
3369
3369
  return selectPreferredTranscriptText(finalText, nextPartial);
3370
3370
  };
3371
3371
 
3372
+ // src/types.ts
3373
+ var ttsAdapterSessionCanCancel = (session) => typeof session.cancel === "function";
3374
+
3372
3375
  // src/session.ts
3373
3376
  var DEFAULT_RECONNECT_TIMEOUT = 30000;
3374
3377
  var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
@@ -3789,6 +3792,27 @@ var createVoiceSession = (options) => {
3789
3792
  });
3790
3793
  }
3791
3794
  };
3795
+ const cancelActiveTTS = async (reason) => {
3796
+ const activeSession = ttsSession;
3797
+ const cancelledTurnId = activeTTSTurnId;
3798
+ if (!activeSession || cancelledTurnId === undefined) {
3799
+ return;
3800
+ }
3801
+ activeTTSTurnId = undefined;
3802
+ if (!ttsAdapterSessionCanCancel(activeSession)) {
3803
+ return;
3804
+ }
3805
+ try {
3806
+ await activeSession.cancel(reason);
3807
+ } catch (error) {
3808
+ logger.warn("voice tts adapter cancel failed", {
3809
+ error: toError(error).message,
3810
+ reason,
3811
+ sessionId: options.id,
3812
+ turnId: cancelledTurnId
3813
+ });
3814
+ }
3815
+ };
3792
3816
  const sendAssistantAudio = async (chunk, input) => {
3793
3817
  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));
3794
3818
  await send({
@@ -5097,6 +5121,9 @@ var createVoiceSession = (options) => {
5097
5121
  pushTurnAudio(conditionedAudio);
5098
5122
  }
5099
5123
  if (audioLevel >= turnDetection.speechThreshold) {
5124
+ if (!speechDetected && activeTTSTurnId !== undefined) {
5125
+ cancelActiveTTS("barge-in");
5126
+ }
5100
5127
  speechDetected = true;
5101
5128
  clearSilenceTimer();
5102
5129
  } else if (speechDetected) {
@@ -34559,6 +34586,76 @@ var createVoiceWorkflowContractHandler = (input) => {
34559
34586
  return result;
34560
34587
  };
34561
34588
  };
34589
+ // src/aiVoiceModel.ts
34590
+ var toProviderMessages = (messages) => {
34591
+ const out = [];
34592
+ for (const message of messages) {
34593
+ if (message.role === "tool") {
34594
+ out.push({
34595
+ content: [
34596
+ {
34597
+ content: message.content,
34598
+ tool_use_id: message.toolCallId ?? message.name ?? "",
34599
+ type: "tool_result"
34600
+ }
34601
+ ],
34602
+ role: "user"
34603
+ });
34604
+ continue;
34605
+ }
34606
+ if (message.role === "system") {
34607
+ out.push({ content: message.content, role: "user" });
34608
+ continue;
34609
+ }
34610
+ out.push({ content: message.content, role: message.role });
34611
+ }
34612
+ return out;
34613
+ };
34614
+ var toProviderTools = (tools) => {
34615
+ if (tools.length === 0) {
34616
+ return;
34617
+ }
34618
+ return tools.map((tool) => ({
34619
+ description: tool.description ?? "",
34620
+ input_schema: tool.parameters ?? {
34621
+ properties: {},
34622
+ type: "object"
34623
+ },
34624
+ name: tool.name
34625
+ }));
34626
+ };
34627
+ var createAIVoiceModel = (options) => ({
34628
+ generate: async (input) => {
34629
+ const systemPrompt = input.system ?? options.systemPrompt;
34630
+ const stream = options.provider.stream({
34631
+ messages: toProviderMessages(input.messages),
34632
+ model: options.model,
34633
+ signal: options.signal,
34634
+ systemPrompt,
34635
+ tools: toProviderTools(input.tools)
34636
+ });
34637
+ let assistantText = "";
34638
+ const toolCalls = [];
34639
+ for await (const chunk of stream) {
34640
+ if (chunk.type === "text") {
34641
+ assistantText += chunk.content;
34642
+ } else if (chunk.type === "tool_use") {
34643
+ toolCalls.push({
34644
+ args: chunk.input ?? {},
34645
+ id: chunk.id,
34646
+ name: chunk.name
34647
+ });
34648
+ }
34649
+ }
34650
+ const output = {
34651
+ assistantText
34652
+ };
34653
+ if (toolCalls.length > 0) {
34654
+ output.toolCalls = toolCalls;
34655
+ }
34656
+ return output;
34657
+ }
34658
+ });
34562
34659
  // src/ragTool.ts
34563
34660
  var DEFAULT_TOOL_NAME = "searchKnowledgeBase";
34564
34661
  var DEFAULT_DESCRIPTION = "Search the knowledge base and return short grounded citations. Use this whenever the caller asks a question that may be answered by indexed reference material.";
@@ -44991,6 +45088,7 @@ export {
44991
45088
  verifyVoiceOpsWebhookSignature,
44992
45089
  validateVoiceWorkflowRouteResult,
44993
45090
  validateVoiceObservabilityExportRecord,
45091
+ ttsAdapterSessionCanCancel,
44994
45092
  transcodeTwilioInboundPayloadToPCM16,
44995
45093
  transcodePCMToTwilioOutboundPayload,
44996
45094
  summarizeVoiceTurnQuality,
@@ -45614,6 +45712,7 @@ export {
45614
45712
  createDomainPhraseHints,
45615
45713
  createDomainLexicon,
45616
45714
  createAnthropicVoiceAssistantModel,
45715
+ createAIVoiceModel,
45617
45716
  conditionAudioChunk,
45618
45717
  completeVoiceOpsTask,
45619
45718
  compareVoiceEvalBaseline,
@@ -5337,6 +5337,9 @@ var resolveLogger = (logger) => ({
5337
5337
  ...logger
5338
5338
  });
5339
5339
 
5340
+ // src/types.ts
5341
+ var ttsAdapterSessionCanCancel = (session) => typeof session.cancel === "function";
5342
+
5340
5343
  // src/session.ts
5341
5344
  var DEFAULT_RECONNECT_TIMEOUT = 30000;
5342
5345
  var DEFAULT_MAX_RECONNECT_ATTEMPTS2 = 10;
@@ -5757,6 +5760,27 @@ var createVoiceSession = (options) => {
5757
5760
  });
5758
5761
  }
5759
5762
  };
5763
+ const cancelActiveTTS = async (reason) => {
5764
+ const activeSession = ttsSession;
5765
+ const cancelledTurnId = activeTTSTurnId;
5766
+ if (!activeSession || cancelledTurnId === undefined) {
5767
+ return;
5768
+ }
5769
+ activeTTSTurnId = undefined;
5770
+ if (!ttsAdapterSessionCanCancel(activeSession)) {
5771
+ return;
5772
+ }
5773
+ try {
5774
+ await activeSession.cancel(reason);
5775
+ } catch (error) {
5776
+ logger.warn("voice tts adapter cancel failed", {
5777
+ error: toError(error).message,
5778
+ reason,
5779
+ sessionId: options.id,
5780
+ turnId: cancelledTurnId
5781
+ });
5782
+ }
5783
+ };
5760
5784
  const sendAssistantAudio = async (chunk, input) => {
5761
5785
  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));
5762
5786
  await send({
@@ -7065,6 +7089,9 @@ var createVoiceSession = (options) => {
7065
7089
  pushTurnAudio(conditionedAudio);
7066
7090
  }
7067
7091
  if (audioLevel >= turnDetection.speechThreshold) {
7092
+ if (!speechDetected && activeTTSTurnId !== undefined) {
7093
+ cancelActiveTTS("barge-in");
7094
+ }
7068
7095
  speechDetected = true;
7069
7096
  clearSilenceTimer();
7070
7097
  } else if (speechDetected) {
@@ -13154,8 +13181,9 @@ var runTTSAdapterFixture = async (adapter, fixture, options = {}) => {
13154
13181
  sessionId: `tts-benchmark:${fixture.id}`,
13155
13182
  ...openOptions ?? {}
13156
13183
  });
13184
+ const sessionOn = session.on;
13157
13185
  const unsubscribers = [
13158
- session.on("audio", ({ chunk, format, receivedAt }) => {
13186
+ sessionOn("audio", ({ chunk, format, receivedAt }) => {
13159
13187
  const normalizedChunk = chunk instanceof Uint8Array ? chunk : chunk instanceof ArrayBuffer ? new Uint8Array(chunk) : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
13160
13188
  audioChunkCount += 1;
13161
13189
  totalAudioBytes += normalizedChunk.byteLength;
@@ -13175,10 +13203,10 @@ var runTTSAdapterFixture = async (adapter, fixture, options = {}) => {
13175
13203
  }, options.interruptAfterFirstAudioMs);
13176
13204
  }
13177
13205
  }),
13178
- session.on("error", () => {
13206
+ sessionOn("error", () => {
13179
13207
  errorCount += 1;
13180
13208
  }),
13181
- session.on("close", () => {
13209
+ sessionOn("close", () => {
13182
13210
  closeCount += 1;
13183
13211
  closed = true;
13184
13212
  closedAt = Date.now();
package/dist/types.d.ts CHANGED
@@ -166,8 +166,12 @@ export type TTSSessionEventMap = {
166
166
  export type TTSAdapterSession = {
167
167
  on: <K extends keyof TTSSessionEventMap>(event: K, handler: (payload: TTSSessionEventMap[K]) => void | Promise<void>) => () => void;
168
168
  send: (text: string) => Promise<void>;
169
+ cancel?: (reason?: string) => Promise<void>;
169
170
  close: (reason?: string) => Promise<void>;
170
171
  };
172
+ export declare const ttsAdapterSessionCanCancel: (session: TTSAdapterSession) => session is TTSAdapterSession & {
173
+ cancel: (reason?: string) => Promise<void>;
174
+ };
171
175
  export type TTSAdapterOpenOptions = {
172
176
  sessionId: string;
173
177
  lexicon?: VoiceLexiconEntry[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.475",
3
+ "version": "0.0.22-beta.476",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -160,7 +160,7 @@
160
160
  "bench:stt": "bun run ./scripts/benchmark-stt.ts all",
161
161
  "bench:assemblyai:sessions": "bun run ./scripts/benchmark-session.ts assemblyai",
162
162
  "bench:openai:sessions": "bun run ./scripts/benchmark-session.ts openai",
163
- "build": "bun run ./scripts/build-htmx-bootstrap-asset.ts && rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/media && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
163
+ "build": "bun run ./scripts/build-htmx-bootstrap-asset.ts && rm -rf dist && bun build ./src/index.ts ./src/client/index.ts ./src/react/index.ts ./src/vue/index.ts ./src/svelte/index.ts ./src/angular/index.ts ./src/testing/index.ts --outdir dist --target bun --external elysia --external react --external vue --external @angular/core --external @absolutejs/absolute --external @absolutejs/ai --external @absolutejs/media && bun build ./src/client/htmxBootstrap.ts --outdir dist/client --target browser --format esm && tsc --emitDeclarationOnly --project tsconfig.json",
164
164
  "format": "prettier --write \"./**/*.{js,jsx,ts,tsx,json,md}\"",
165
165
  "lint": "eslint ./src",
166
166
  "release": "bun run format && bun run build && bun publish",
@@ -229,12 +229,16 @@
229
229
  },
230
230
  "peerDependencies": {
231
231
  "@absolutejs/absolute": ">=0.19.0-beta.646",
232
+ "@absolutejs/ai": ">=0.0.5",
232
233
  "@angular/core": ">=21.0.0",
233
234
  "elysia": ">=1.4.18",
234
235
  "react": ">=19.0.0",
235
236
  "vue": ">=3.5.0"
236
237
  },
237
238
  "peerDependenciesMeta": {
239
+ "@absolutejs/ai": {
240
+ "optional": true
241
+ },
238
242
  "@angular/core": {
239
243
  "optional": true
240
244
  },
@@ -250,6 +254,7 @@
250
254
  },
251
255
  "devDependencies": {
252
256
  "@absolutejs/absolute": "0.19.0-beta.646",
257
+ "@absolutejs/ai": "0.0.5",
253
258
  "@angular/core": "^21.0.0",
254
259
  "@types/bun": "1.3.9",
255
260
  "@types/react": "19.2.0",