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

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/index.d.ts CHANGED
@@ -71,6 +71,8 @@ export { createVoiceSessionListRoutes, createVoiceSessionReplayHTMLHandler, crea
71
71
  export { createVoiceAgent, createVoiceAgentSquad, createVoiceAgentTool, } from "./agent";
72
72
  export { createPersonaVoiceCaller, createScriptedVoiceCaller, renderVoiceSimulationTranscript, runVoiceConversationSimulation, } from "./conversationSimulator";
73
73
  export type { RunVoiceConversationSimulationInput, VoiceConversationSimulationEndedReason, VoiceConversationSimulationResult, VoicePersonaCallerCompletion, VoiceScriptedCallerStep, VoiceSimulatedSpeaker, VoiceSimulatedTurn, VoiceSimulatorCaller, VoiceSimulatorCallerModel, VoiceSimulatorCallerReply, } from "./conversationSimulator";
74
+ export { createVoiceMCPToolset } from "./mcpToolset";
75
+ export type { CreateVoiceMCPToolsetOptions, MCPClientLike, MCPToolCallResult, MCPToolContentBlock, MCPToolDefinition, VoiceMCPToolResult, } from "./mcpToolset";
74
76
  export { createAIVoiceModel } from "./aiVoiceModel";
75
77
  export type { CreateAIVoiceModelOptions } from "./aiVoiceModel";
76
78
  export { createVoiceAIJudgeCompletion, createVoiceLLMJudge, } from "./llmJudge";
@@ -353,6 +355,8 @@ export { compileVoicePathwayToAssistant } from "./pathwayCompiler";
353
355
  export type { CompileVoicePathwayOptions, VoicePathwayCompiledAssistant, VoicePathwayCompilerToolDefinition, } from "./pathwayCompiler";
354
356
  export { renderVoicePathwayMermaid, renderVoicePathwayText, visualizeVoicePathway, } from "./pathwayVisualizer";
355
357
  export type { VoicePathwayVisualization } from "./pathwayVisualizer";
358
+ export { generateVoicePathwayFromPrompt } from "./pathwayGenerator";
359
+ export type { GenerateVoicePathwayInput, GenerateVoicePathwayResult, VoicePathwayGeneratorCompletion, } from "./pathwayGenerator";
356
360
  export { createVoiceCRMRegistry } from "./crmContract";
357
361
  export type { CreateVoiceCRMRegistryOptions, VoiceCRMCallActivityInput, VoiceCRMContactSummary, VoiceCRMContract, VoiceCRMLeadInput, VoiceCRMNoteInput, VoiceCRMRegistry, VoiceCRMTaskInput, } from "./crmContract";
358
362
  export { createInMemoryVoiceCallerCRMLinkCache, createVoiceCallerCRMLinker, } from "./callerCRMLinker";
package/dist/index.js CHANGED
@@ -35415,6 +35415,52 @@ Respond with only your spoken line. When your goal is met or you want to hang up
35415
35415
  persona: options.persona
35416
35416
  };
35417
35417
  };
35418
+ // src/mcpToolset.ts
35419
+ var flattenContent = (result) => {
35420
+ const blocks = result.content ?? [];
35421
+ const text = blocks.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join(`
35422
+ `).trim();
35423
+ if (text.length > 0)
35424
+ return text;
35425
+ if (result.structuredContent !== undefined) {
35426
+ return JSON.stringify(result.structuredContent);
35427
+ }
35428
+ return "";
35429
+ };
35430
+ var createVoiceMCPToolset = async (options) => {
35431
+ const prefix = options.namePrefix ?? "";
35432
+ const allowed = options.allowedTools ? new Set(options.allowedTools) : undefined;
35433
+ const blocked = options.blockedTools ? new Set(options.blockedTools) : undefined;
35434
+ const listed = await Promise.resolve(options.client.listTools());
35435
+ const tools = [];
35436
+ for (const definition of listed.tools) {
35437
+ if (allowed && !allowed.has(definition.name))
35438
+ continue;
35439
+ if (blocked && blocked.has(definition.name))
35440
+ continue;
35441
+ const exposedName = `${prefix}${definition.name}`;
35442
+ tools.push(createVoiceAgentTool({
35443
+ ...definition.description !== undefined ? { description: definition.description } : {},
35444
+ execute: async ({ args }) => {
35445
+ const raw = await Promise.resolve(options.client.callTool({
35446
+ arguments: args,
35447
+ name: definition.name
35448
+ }));
35449
+ const result = {
35450
+ isError: raw.isError === true,
35451
+ raw,
35452
+ text: flattenContent(raw),
35453
+ ...raw.structuredContent !== undefined ? { structuredContent: raw.structuredContent } : {}
35454
+ };
35455
+ return result;
35456
+ },
35457
+ name: exposedName,
35458
+ ...definition.inputSchema !== undefined ? { parameters: definition.inputSchema } : {},
35459
+ resultToMessage: options.resultToMessage ?? ((result) => result.isError ? `Tool error: ${result.text || "unknown error"}` : result.text || "(no output)")
35460
+ }));
35461
+ }
35462
+ return tools;
35463
+ };
35418
35464
  // src/aiVoiceModel.ts
35419
35465
  var toProviderMessages = (messages) => {
35420
35466
  const out = [];
@@ -51531,6 +51577,119 @@ var visualizeVoicePathway = (pathway) => ({
51531
51577
  mermaid: renderVoicePathwayMermaid(pathway),
51532
51578
  text: renderVoicePathwayText(pathway)
51533
51579
  });
51580
+ // src/pathwayGenerator.ts
51581
+ var SYSTEM_PROMPT = `You design conversation pathways for voice agents as strict JSON.
51582
+
51583
+ A pathway is a state machine. Output ONLY a JSON object with this shape:
51584
+ {
51585
+ "id": "kebab-case-id",
51586
+ "label": "Human readable label",
51587
+ "entryStateId": "<id of the first state>",
51588
+ "slots": [
51589
+ { "id": "slot_id", "type": "string|number|boolean|date|time|phone|email|currency|choice", "prompt": "what to ask", "required": true, "choices": ["a","b"] }
51590
+ ],
51591
+ "states": [
51592
+ {
51593
+ "id": "state_id",
51594
+ "label": "Label",
51595
+ "kind": "entry|collect|branch|action|terminal",
51596
+ "actions": [
51597
+ { "kind": "say", "text": "..." },
51598
+ { "kind": "collect-slot", "slotId": "slot_id" },
51599
+ { "kind": "call-tool", "toolId": "tool_id", "argsFromSlots": ["slot_id"] },
51600
+ { "kind": "transfer", "destination": "..." },
51601
+ { "kind": "end-call", "reason": "..." }
51602
+ ],
51603
+ "transitions": [
51604
+ { "to": "next_state", "condition": { "kind": "always" } },
51605
+ { "to": "x", "condition": { "kind": "slot-filled", "slotId": "slot_id" } },
51606
+ { "to": "y", "condition": { "kind": "slot-equals", "slotId": "slot_id", "value": "yes" } },
51607
+ { "to": "z", "condition": { "kind": "fallback" } }
51608
+ ]
51609
+ }
51610
+ ],
51611
+ "tools": [ { "id": "tool_id", "description": "..." } ]
51612
+ }
51613
+
51614
+ Hard rules:
51615
+ - Exactly one entry state; entryStateId must reference a real state.
51616
+ - At least one terminal state (kind "terminal" or a state with no transitions) must be reachable from entry.
51617
+ - Every transition "to" must reference a defined state id.
51618
+ - Every slotId referenced in actions/conditions must be defined in "slots".
51619
+ - A "fallback" transition, if present, must be the LAST transition in its state.
51620
+ - Do not invent extra fields. Output JSON only \u2014 no prose, no markdown fences.`;
51621
+ var slugify = (value) => value.toLowerCase().trim().replace(/[^a-z0-9]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 60) || "generated-pathway";
51622
+ var extractJson4 = (raw) => {
51623
+ const trimmed = raw.trim();
51624
+ if (!trimmed)
51625
+ throw new Error("Pathway generator returned an empty response");
51626
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(trimmed);
51627
+ const candidate = fenced ? fenced[1].trim() : trimmed;
51628
+ try {
51629
+ return JSON.parse(candidate);
51630
+ } catch {
51631
+ const start = candidate.indexOf("{");
51632
+ const end = candidate.lastIndexOf("}");
51633
+ if (start >= 0 && end > start) {
51634
+ return JSON.parse(candidate.slice(start, end + 1));
51635
+ }
51636
+ throw new Error(`Pathway generator response was not valid JSON: ${raw.slice(0, 200)}`);
51637
+ }
51638
+ };
51639
+ var coercePathway = (parsed, fallbackId) => {
51640
+ if (!parsed || typeof parsed !== "object") {
51641
+ throw new Error("Pathway generator response is not a JSON object");
51642
+ }
51643
+ const root = parsed;
51644
+ return {
51645
+ entryStateId: String(root.entryStateId ?? ""),
51646
+ id: typeof root.id === "string" && root.id.length > 0 ? root.id : fallbackId,
51647
+ label: String(root.label ?? "Generated pathway"),
51648
+ slots: Array.isArray(root.slots) ? root.slots : [],
51649
+ states: Array.isArray(root.states) ? root.states : [],
51650
+ ...Array.isArray(root.tools) ? { tools: root.tools } : {},
51651
+ ...root.metadata && typeof root.metadata === "object" ? { metadata: root.metadata } : {}
51652
+ };
51653
+ };
51654
+ var generateVoicePathwayFromPrompt = async (input) => {
51655
+ const fallbackId = input.id ?? slugify(input.description);
51656
+ const maxRepairs = input.maxRepairAttempts ?? 2;
51657
+ const systemPrompt = input.guidance ? `${SYSTEM_PROMPT}
51658
+
51659
+ Additional guidance:
51660
+ ${input.guidance}` : SYSTEM_PROMPT;
51661
+ const rawOutputs = [];
51662
+ let lastReport = null;
51663
+ let lastPathway = null;
51664
+ for (let attempt = 0;attempt <= maxRepairs; attempt += 1) {
51665
+ const prompt = attempt === 0 ? `Build a voice pathway for:
51666
+ ${input.description}
51667
+
51668
+ Suggested id: ${fallbackId}` : `The previous pathway JSON failed validation with these errors:
51669
+ ${lastReport.issues.filter((issue) => issue.severity === "error").map((issue) => `- ${issue.message}`).join(`
51670
+ `)}
51671
+
51672
+ Here was your previous output:
51673
+ ${rawOutputs.at(-1)}
51674
+
51675
+ Return a corrected pathway JSON that fixes every error.`;
51676
+ const raw = await input.completion({ prompt, systemPrompt });
51677
+ rawOutputs.push(raw);
51678
+ const pathway = coercePathway(extractJson4(raw), fallbackId);
51679
+ const report = validateVoicePathway(pathway);
51680
+ lastReport = report;
51681
+ lastPathway = pathway;
51682
+ if (report.valid) {
51683
+ return { attempts: attempt + 1, pathway, rawOutputs, report };
51684
+ }
51685
+ }
51686
+ return {
51687
+ attempts: rawOutputs.length,
51688
+ pathway: lastPathway,
51689
+ rawOutputs,
51690
+ report: lastReport
51691
+ };
51692
+ };
51534
51693
  // src/crmContract.ts
51535
51694
  var createVoiceCRMRegistry = (options) => {
51536
51695
  const byVendor = new Map;
@@ -51970,6 +52129,7 @@ export {
51970
52129
  getLatestVoiceTelephonyMediaReport,
51971
52130
  getLatestVoiceBrowserMediaReport,
51972
52131
  getDefaultVoiceTelephonyBenchmarkScenarios,
52132
+ generateVoicePathwayFromPrompt,
51973
52133
  generateVoiceCalendarSlots,
51974
52134
  fromVapiAssistantConfig,
51975
52135
  formatVoiceProofTrendAge,
@@ -52275,6 +52435,7 @@ export {
52275
52435
  createVoiceMemoryAuditEventStore,
52276
52436
  createVoiceMemoryAssistantMemoryStore,
52277
52437
  createVoiceMediaPipelineRoutes,
52438
+ createVoiceMCPToolset,
52278
52439
  createVoiceLiveOpsRoutes,
52279
52440
  createVoiceLiveOpsController,
52280
52441
  createVoiceLiveMonitorRoutes,
@@ -0,0 +1,58 @@
1
+ import { type VoiceAgentTool } from "./agent";
2
+ import type { VoiceSessionRecord } from "./types";
3
+ /**
4
+ * Minimal structural shapes from the Model Context Protocol. Any MCP client
5
+ * (`@modelcontextprotocol/sdk` over stdio / SSE / streamable-HTTP, or a custom
6
+ * transport) that exposes `listTools` + `callTool` satisfies this — voice does
7
+ * not bundle an MCP SDK.
8
+ */
9
+ export type MCPToolDefinition = {
10
+ name: string;
11
+ description?: string;
12
+ inputSchema?: Record<string, unknown>;
13
+ };
14
+ export type MCPToolContentBlock = {
15
+ type: "text";
16
+ text: string;
17
+ } | {
18
+ type: string;
19
+ [key: string]: unknown;
20
+ };
21
+ export type MCPToolCallResult = {
22
+ content?: MCPToolContentBlock[];
23
+ structuredContent?: unknown;
24
+ isError?: boolean;
25
+ };
26
+ export type MCPClientLike = {
27
+ listTools: () => Promise<{
28
+ tools: MCPToolDefinition[];
29
+ }> | {
30
+ tools: MCPToolDefinition[];
31
+ };
32
+ callTool: (input: {
33
+ name: string;
34
+ arguments?: Record<string, unknown>;
35
+ }) => Promise<MCPToolCallResult> | MCPToolCallResult;
36
+ };
37
+ export type VoiceMCPToolResult = {
38
+ text: string;
39
+ structuredContent?: unknown;
40
+ isError: boolean;
41
+ raw: MCPToolCallResult;
42
+ };
43
+ export type CreateVoiceMCPToolsetOptions = {
44
+ client: MCPClientLike;
45
+ /** Prefix applied to every exposed tool name (e.g. "mcp_"). */
46
+ namePrefix?: string;
47
+ /** Only expose tools whose (unprefixed) name is in this allow-list. */
48
+ allowedTools?: ReadonlyArray<string>;
49
+ /** Drop tools whose (unprefixed) name is in this block-list. */
50
+ blockedTools?: ReadonlyArray<string>;
51
+ /** Override how an MCP result is flattened to the assistant-visible string. */
52
+ resultToMessage?: (result: VoiceMCPToolResult) => string;
53
+ };
54
+ /**
55
+ * Bridges the tools exposed by an MCP server into `VoiceAgentTool`s. Call once
56
+ * at setup; the returned array spreads straight into `createVoiceAgent({ tools })`.
57
+ */
58
+ export declare const createVoiceMCPToolset: <TContext = unknown, TSession extends VoiceSessionRecord = VoiceSessionRecord>(options: CreateVoiceMCPToolsetOptions) => Promise<VoiceAgentTool<TContext, TSession, Record<string, unknown>, VoiceMCPToolResult>[]>;
@@ -0,0 +1,27 @@
1
+ import { type VoicePathway, type VoicePathwayValidationReport } from "./pathway";
2
+ export type VoicePathwayGeneratorCompletion = (input: {
3
+ prompt: string;
4
+ systemPrompt: string;
5
+ }) => Promise<string>;
6
+ export type GenerateVoicePathwayInput = {
7
+ /** Plain-text description of the agent / flow to build. */
8
+ description: string;
9
+ completion: VoicePathwayGeneratorCompletion;
10
+ /** Suggested pathway id (slugified). Defaults to "generated-pathway". */
11
+ id?: string;
12
+ /** Extra guidance appended to the system prompt. */
13
+ guidance?: string;
14
+ /**
15
+ * If the first attempt fails validation, retry this many times feeding the
16
+ * issues back to the model. Defaults to 2.
17
+ */
18
+ maxRepairAttempts?: number;
19
+ };
20
+ export type GenerateVoicePathwayResult = {
21
+ pathway: VoicePathway;
22
+ report: VoicePathwayValidationReport;
23
+ attempts: number;
24
+ /** Raw model outputs from each attempt, for debugging. */
25
+ rawOutputs: string[];
26
+ };
27
+ export declare const generateVoicePathwayFromPrompt: (input: GenerateVoicePathwayInput) => Promise<GenerateVoicePathwayResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absolutejs/voice",
3
- "version": "0.0.22-beta.520",
3
+ "version": "0.0.22-beta.522",
4
4
  "description": "Voice primitives and Elysia plugin for AbsoluteJS",
5
5
  "repository": {
6
6
  "type": "git",