@absolutejs/voice 0.0.22-beta.519 → 0.0.22-beta.520

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,73 @@
1
+ import type { VoiceAgent, VoiceAgentRunResult } from "./agent";
2
+ import type { VoiceSessionRecord } from "./types";
3
+ export type VoiceSimulatedSpeaker = "caller" | "agent";
4
+ export type VoiceSimulatedTurn = {
5
+ role: VoiceSimulatedSpeaker;
6
+ text: string;
7
+ at: number;
8
+ };
9
+ export type VoiceSimulatorCallerReply = {
10
+ text: string;
11
+ /** When true the synthetic caller ends the call after this utterance. */
12
+ done?: boolean;
13
+ };
14
+ export type VoiceSimulatorCallerModel = (input: {
15
+ persona: string;
16
+ transcript: ReadonlyArray<VoiceSimulatedTurn>;
17
+ turnIndex: number;
18
+ }) => Promise<VoiceSimulatorCallerReply> | VoiceSimulatorCallerReply;
19
+ export type VoiceSimulatorCaller = {
20
+ kind: "script";
21
+ /** Fixed caller utterances, delivered in order. */
22
+ utterances: ReadonlyArray<string>;
23
+ } | {
24
+ kind: "model";
25
+ /** System description of who the caller is and what they want. */
26
+ persona: string;
27
+ model: VoiceSimulatorCallerModel;
28
+ /** Hard cap on caller turns (defaults to maxTurns). */
29
+ maxCallerTurns?: number;
30
+ };
31
+ export type VoiceConversationSimulationEndedReason = "agent-complete" | "caller-hung-up" | "max-turns" | "script-exhausted";
32
+ export type VoiceConversationSimulationResult<TResult = unknown> = {
33
+ transcript: VoiceSimulatedTurn[];
34
+ turnCount: number;
35
+ endedReason: VoiceConversationSimulationEndedReason;
36
+ agentResults: VoiceAgentRunResult<TResult>[];
37
+ };
38
+ export type RunVoiceConversationSimulationInput<TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown> = {
39
+ agent: Pick<VoiceAgent<TContext, TSession, TResult>, "run">;
40
+ caller: VoiceSimulatorCaller;
41
+ context?: TContext;
42
+ session?: TSession;
43
+ sessionId?: string;
44
+ /** Hard cap on caller↔agent exchanges (default 12). */
45
+ maxTurns?: number;
46
+ /** Stop early when the agent route returns `complete: true` (default true). */
47
+ stopOnAgentComplete?: boolean;
48
+ now?: () => number;
49
+ generateId?: () => string;
50
+ };
51
+ export declare const runVoiceConversationSimulation: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord, TResult = unknown>(input: RunVoiceConversationSimulationInput<TContext, TSession, TResult>) => Promise<VoiceConversationSimulationResult<TResult>>;
52
+ export declare const renderVoiceSimulationTranscript: (transcript: ReadonlyArray<VoiceSimulatedTurn>, labels?: {
53
+ caller?: string;
54
+ agent?: string;
55
+ }) => string;
56
+ export type VoiceScriptedCallerStep = string;
57
+ /** Convenience: build a deterministic scripted caller from a list of lines. */
58
+ export declare const createScriptedVoiceCaller: (utterances: ReadonlyArray<VoiceScriptedCallerStep>) => VoiceSimulatorCaller;
59
+ export type VoicePersonaCallerCompletion = (input: {
60
+ prompt: string;
61
+ systemPrompt: string;
62
+ }) => Promise<string>;
63
+ /**
64
+ * Builds an LLM-backed synthetic caller. The completion returns the caller's
65
+ * next line; appending the sentinel `[[END]]` (case-insensitive) signals the
66
+ * caller wants to hang up.
67
+ */
68
+ export declare const createPersonaVoiceCaller: (options: {
69
+ persona: string;
70
+ completion: VoicePersonaCallerCompletion;
71
+ maxCallerTurns?: number;
72
+ endSentinel?: string;
73
+ }) => VoiceSimulatorCaller;
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 { createPersonaVoiceCaller, createScriptedVoiceCaller, renderVoiceSimulationTranscript, runVoiceConversationSimulation, } from "./conversationSimulator";
73
+ export type { RunVoiceConversationSimulationInput, VoiceConversationSimulationEndedReason, VoiceConversationSimulationResult, VoicePersonaCallerCompletion, VoiceScriptedCallerStep, VoiceSimulatedSpeaker, VoiceSimulatedTurn, VoiceSimulatorCaller, VoiceSimulatorCallerModel, VoiceSimulatorCallerReply, } from "./conversationSimulator";
72
74
  export { createAIVoiceModel } from "./aiVoiceModel";
73
75
  export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
74
76
  export { createVoiceAIJudgeCompletion, createVoiceLLMJudge, } from "./llmJudge";
package/dist/index.js CHANGED
@@ -35297,6 +35297,124 @@ var createVoiceWorkflowContractHandler = (input) => {
35297
35297
  return result;
35298
35298
  };
35299
35299
  };
35300
+ // src/conversationSimulator.ts
35301
+ var createStubApi = (sessionId) => ({ id: sessionId });
35302
+ var callerTurnText = async (caller, transcript, turnIndex) => {
35303
+ if (caller.kind === "script") {
35304
+ const text = caller.utterances[turnIndex];
35305
+ if (text === undefined)
35306
+ return null;
35307
+ return {
35308
+ done: turnIndex === caller.utterances.length - 1,
35309
+ text
35310
+ };
35311
+ }
35312
+ const cap = caller.maxCallerTurns;
35313
+ if (cap !== undefined && turnIndex >= cap)
35314
+ return null;
35315
+ return Promise.resolve(caller.model({ persona: caller.persona, transcript, turnIndex }));
35316
+ };
35317
+ var runVoiceConversationSimulation = async (input) => {
35318
+ const now = input.now ?? (() => Date.now());
35319
+ const generateId = input.generateId ?? (() => `sim_${Math.random().toString(36).slice(2, 10)}`);
35320
+ const maxTurns = input.maxTurns ?? 12;
35321
+ const stopOnComplete = input.stopOnAgentComplete !== false;
35322
+ const sessionId = input.sessionId ?? `sim-${generateId()}`;
35323
+ const session = input.session ?? createVoiceSessionRecord(sessionId);
35324
+ const context = input.context ?? {};
35325
+ const api = createStubApi(sessionId);
35326
+ const transcript = [];
35327
+ const agentResults = [];
35328
+ let endedReason = "max-turns";
35329
+ for (let turnIndex = 0;turnIndex < maxTurns; turnIndex += 1) {
35330
+ const callerReply = await callerTurnText(input.caller, transcript, turnIndex);
35331
+ if (!callerReply) {
35332
+ endedReason = input.caller.kind === "script" ? "script-exhausted" : "max-turns";
35333
+ break;
35334
+ }
35335
+ const callerAt = now();
35336
+ transcript.push({ at: callerAt, role: "caller", text: callerReply.text });
35337
+ const userTranscript = {
35338
+ id: generateId(),
35339
+ isFinal: true,
35340
+ startedAtMs: callerAt,
35341
+ endedAtMs: callerAt,
35342
+ text: callerReply.text
35343
+ };
35344
+ const turn = {
35345
+ committedAt: callerAt,
35346
+ id: generateId(),
35347
+ text: callerReply.text,
35348
+ transcripts: [userTranscript]
35349
+ };
35350
+ const result = await input.agent.run({
35351
+ api,
35352
+ context,
35353
+ session,
35354
+ turn
35355
+ });
35356
+ agentResults.push(result);
35357
+ const assistantText = result.assistantText ?? "";
35358
+ if (assistantText.trim().length > 0) {
35359
+ transcript.push({ at: now(), role: "agent", text: assistantText });
35360
+ }
35361
+ const committedTurn = {
35362
+ ...turn,
35363
+ assistantText: assistantText || undefined,
35364
+ ...result.citations && result.citations.length > 0 ? { citations: [...result.citations] } : {}
35365
+ };
35366
+ session.turns = [...session.turns, committedTurn];
35367
+ if (stopOnComplete && result.complete) {
35368
+ endedReason = "agent-complete";
35369
+ break;
35370
+ }
35371
+ if (callerReply.done) {
35372
+ endedReason = "caller-hung-up";
35373
+ break;
35374
+ }
35375
+ }
35376
+ return {
35377
+ agentResults,
35378
+ endedReason,
35379
+ transcript,
35380
+ turnCount: agentResults.length
35381
+ };
35382
+ };
35383
+ var renderVoiceSimulationTranscript = (transcript, labels = {}) => {
35384
+ const callerLabel = labels.caller ?? "Caller";
35385
+ const agentLabel = labels.agent ?? "Agent";
35386
+ return transcript.map((turn) => `${turn.role === "caller" ? callerLabel : agentLabel}: ${turn.text}`).join(`
35387
+ `);
35388
+ };
35389
+ var createScriptedVoiceCaller = (utterances) => ({
35390
+ kind: "script",
35391
+ utterances
35392
+ });
35393
+ var createPersonaVoiceCaller = (options) => {
35394
+ const endSentinel = options.endSentinel ?? "[[END]]";
35395
+ return {
35396
+ kind: "model",
35397
+ ...options.maxCallerTurns !== undefined ? { maxCallerTurns: options.maxCallerTurns } : {},
35398
+ model: async ({ persona, transcript }) => {
35399
+ const history = transcript.map((turn) => `${turn.role === "caller" ? "You" : "Agent"}: ${turn.text}`).join(`
35400
+ `);
35401
+ const raw = await options.completion({
35402
+ prompt: history.length > 0 ? `Conversation so far:
35403
+ ${history}
35404
+
35405
+ Your next line:` : "Start the call with your opening line:",
35406
+ systemPrompt: `You are role-playing a caller in a voice conversation. ${persona}
35407
+ Respond with only your spoken line. When your goal is met or you want to hang up, end your line with ${endSentinel}.`
35408
+ });
35409
+ const done = raw.includes(endSentinel);
35410
+ return {
35411
+ done,
35412
+ text: raw.replaceAll(endSentinel, "").trim()
35413
+ };
35414
+ },
35415
+ persona: options.persona
35416
+ };
35417
+ };
35300
35418
  // src/aiVoiceModel.ts
35301
35419
  var toProviderMessages = (messages) => {
35302
35420
  const out = [];
@@ -51657,6 +51775,7 @@ export {
51657
51775
  runVoicePhoneAgentProductionSmokeContract,
51658
51776
  runVoiceOutcomeContractSuite,
51659
51777
  runVoiceMultilingualProof,
51778
+ runVoiceConversationSimulation,
51660
51779
  runVoiceCommandProofTargets,
51661
51780
  runVoiceCommandProofTarget,
51662
51781
  runVoiceCampaignReadinessProof,
@@ -51702,6 +51821,7 @@ export {
51702
51821
  renderVoiceSloReadinessThresholdMarkdown,
51703
51822
  renderVoiceSloReadinessThresholdHTML,
51704
51823
  renderVoiceSloCalibrationMarkdown,
51824
+ renderVoiceSimulationTranscript,
51705
51825
  renderVoiceSimulationSuiteHTML,
51706
51826
  renderVoiceSessionsHTML,
51707
51827
  renderVoiceSessionObservabilityMarkdown,
@@ -52292,6 +52412,7 @@ export {
52292
52412
  createStoredVoiceIncidentBundleArtifact,
52293
52413
  createStoredVoiceExternalObjectMap,
52294
52414
  createStoredVoiceCallReviewArtifact,
52415
+ createScriptedVoiceCaller,
52295
52416
  createRiskyTurnCorrectionHandler,
52296
52417
  createRegexSemanticTurnDetector,
52297
52418
  createPunctuationSemanticTurnDetector,
@@ -52299,6 +52420,7 @@ export {
52299
52420
  createPlivoVoiceResponse,
52300
52421
  createPlivoMediaStreamBridge,
52301
52422
  createPhraseHintCorrectionHandler,
52423
+ createPersonaVoiceCaller,
52302
52424
  createOpenAIVoiceTTS,
52303
52425
  createOpenAIVoiceAssistantModel,
52304
52426
  createMonologueAMDDetector,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.519",
3
+ "version": "0.0.22-beta.520",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",