@voicethere/agent 0.5.6 → 0.7.0

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.
Files changed (38) hide show
  1. package/dist/agent.js +63 -2
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/protocol.d.ts +99 -2
  7. package/dist/protocol.d.ts.map +1 -1
  8. package/dist/protocol.js +4 -0
  9. package/dist/protocol.js.map +1 -1
  10. package/dist/runtime.d.ts +45 -1
  11. package/dist/runtime.d.ts.map +1 -1
  12. package/dist/runtime.js +230 -0
  13. package/dist/runtime.js.map +1 -1
  14. package/dist/templates/echo/agent.js +61 -2
  15. package/dist/templates/echo-dc/agent.js +61 -2
  16. package/dist/templates/game-sync/agent.js +125 -33
  17. package/dist/templates/index.d.ts +2 -0
  18. package/dist/templates/index.d.ts.map +1 -1
  19. package/dist/templates/index.js +14 -0
  20. package/dist/templates/index.js.map +1 -1
  21. package/dist/templates/positional-tts/agent.js +810 -0
  22. package/dist/templates/recording-consent/agent.js +61 -2
  23. package/dist/templates/registry.d.ts.map +1 -1
  24. package/dist/templates/registry.js +17 -0
  25. package/dist/templates/registry.js.map +1 -1
  26. package/dist/templates/voice-showcase/agent.js +102 -21
  27. package/dist/templates/voice-starter/agent.js +61 -2
  28. package/dist/templates/webhooks/agent.js +61 -2
  29. package/dist/templates/webhooks-redis/agent.js +61 -2
  30. package/package.json +10 -4
  31. package/templates/README.md +12 -6
  32. package/templates/game-sync-world-layout.ts +45 -0
  33. package/templates/game-sync.ts +39 -24
  34. package/templates/mix-smoke.ts +224 -0
  35. package/templates/positional-tts/agent.ts +115 -0
  36. package/templates/positional-tts/orbit.ts +11 -0
  37. package/templates/voice-showcase/agent.ts +21 -22
  38. package/templates/voice-showcase/delivery.ts +57 -0
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Positional TTS template — each listener hears TTS orbiting their own pose.
3
+ *
4
+ * Requires voice or Voice+Data runner mode ({@link isTtsPoseAvailable}). Mix group
5
+ * APIs still require Voice+Data ({@link isMixAvailable}).
6
+ *
7
+ * Build:
8
+ * npx @voicethere/agent build --entry templates/positional-tts/agent.ts
9
+ */
10
+ import {
11
+ agentLog,
12
+ clearTtsPose,
13
+ defineAgent,
14
+ isTtsPoseAvailable,
15
+ parseChatText,
16
+ setPositionalMixing,
17
+ setTtsPose,
18
+ speak,
19
+ } from "@voicethere/agent";
20
+
21
+ import { orbitTtsPose } from "./orbit.js";
22
+
23
+ const ORBIT_INTERVAL_MS = 50;
24
+
25
+ const orbitTimers = new Map<string, ReturnType<typeof setInterval>>();
26
+ const sessionStartTimes = new Map<string, number>();
27
+
28
+ function clearOrbit(sessionId: string): void {
29
+ const timer = orbitTimers.get(sessionId);
30
+ if (timer) {
31
+ clearInterval(timer);
32
+ orbitTimers.delete(sessionId);
33
+ }
34
+ sessionStartTimes.delete(sessionId);
35
+ }
36
+
37
+ async function safeMixCall(
38
+ sessionId: string,
39
+ label: string,
40
+ fn: () => Promise<{ ok: boolean; reason?: string }>,
41
+ ): Promise<void> {
42
+ try {
43
+ const result = await fn();
44
+ if (!result.ok) {
45
+ agentLog(
46
+ "warn",
47
+ `positional-tts ${label} failed: ${result.reason}`,
48
+ sessionId,
49
+ );
50
+ }
51
+ } catch (err) {
52
+ agentLog(
53
+ "warn",
54
+ `positional-tts ${label} error: ${String(err)}`,
55
+ sessionId,
56
+ );
57
+ }
58
+ }
59
+
60
+ defineAgent({
61
+ onAgentStart() {
62
+ orbitTimers.clear();
63
+ sessionStartTimes.clear();
64
+ },
65
+
66
+ onSessionStart(ctx) {
67
+ const { sessionId } = ctx;
68
+
69
+ if (!isTtsPoseAvailable(ctx)) {
70
+ agentLog(
71
+ "warn",
72
+ "positional-tts requires voice or Voice+Data — TTS pose APIs unavailable",
73
+ sessionId,
74
+ );
75
+ return;
76
+ }
77
+
78
+ void (async () => {
79
+ await safeMixCall(sessionId, "setPositionalMixing", () =>
80
+ setPositionalMixing(true),
81
+ );
82
+
83
+ const startMs = Date.now();
84
+ sessionStartTimes.set(sessionId, startMs);
85
+
86
+ const timer = setInterval(() => {
87
+ const started = sessionStartTimes.get(sessionId);
88
+ if (!started) return;
89
+ const elapsedSec = (Date.now() - started) / 1000;
90
+ void safeMixCall(sessionId, "setTtsPose", () =>
91
+ setTtsPose(sessionId, orbitTtsPose(elapsedSec)),
92
+ );
93
+ }, ORBIT_INTERVAL_MS);
94
+
95
+ orbitTimers.set(sessionId, timer);
96
+ speak(sessionId, "I'll circle around you.");
97
+ })();
98
+ },
99
+
100
+ onUserSpeechFinal({ sessionId, text }) {
101
+ speak(sessionId, `You said: ${text}`);
102
+ },
103
+
104
+ onDataChannelMessage(ctx) {
105
+ const text = parseChatText(ctx.message);
106
+ if (!text) return;
107
+ speak(ctx.sessionId, `You said: ${text}`);
108
+ },
109
+
110
+ onSessionEnd({ sessionId }) {
111
+ clearOrbit(sessionId);
112
+ void safeMixCall(sessionId, "clearTtsPose", () => clearTtsPose(sessionId));
113
+ agentLog("info", `positional-tts session_end ${sessionId}`, sessionId);
114
+ },
115
+ });
@@ -0,0 +1,11 @@
1
+ import type { MixPose } from "@voicethere/agent";
2
+
3
+ /** Circle in the XZ plane around the listener origin (Y-up, look −Z). */
4
+ export function orbitTtsPose(elapsedSec: number, radius = 2): MixPose {
5
+ const x = Math.cos(elapsedSec) * radius;
6
+ const z = Math.sin(elapsedSec) * radius;
7
+ return {
8
+ position: { x, y: 0, z },
9
+ orientation: { x: 0, y: 0, z: 0, w: 1 },
10
+ };
11
+ }
@@ -19,8 +19,13 @@ import {
19
19
  handleUtterance,
20
20
  resolveWeatherTurn,
21
21
  type ConversationState,
22
- type OutboundMessage,
22
+ type ConversationTurnResult,
23
23
  } from "./conversation.js";
24
+ import {
25
+ applyOutboundOps,
26
+ greetingOps,
27
+ spokenThenPlayOps,
28
+ } from "./delivery.js";
24
29
 
25
30
  const sessions = new Map<string, ConversationState>();
26
31
 
@@ -42,25 +47,23 @@ function relaySpeechEvent(sessionId: string, event: SpeechEvent): void {
42
47
  });
43
48
  }
44
49
 
45
- function deliverMessages(sessionId: string, messages: OutboundMessage[]): void {
46
- for (const message of messages) {
47
- sendToClient(sessionId, message);
48
- }
49
- }
50
-
51
- function speakLines(sessionId: string, lines: string[]): void {
52
- for (const line of lines) {
53
- speak(sessionId, line);
54
- }
50
+ function deliverSpokenThenPlay(
51
+ sessionId: string,
52
+ result: Pick<ConversationTurnResult, "messages" | "speakLines">,
53
+ ): void {
54
+ applyOutboundOps(
55
+ sessionId,
56
+ spokenThenPlayOps(result.messages, result.speakLines),
57
+ { sendToClient, speak },
58
+ );
55
59
  }
56
60
 
57
61
  async function applyTurn(
58
62
  sessionId: string,
59
- result: Awaited<ReturnType<typeof handleUtterance>>,
63
+ result: ConversationTurnResult,
60
64
  ): Promise<void> {
61
65
  sessions.set(sessionId, result.state);
62
- speakLines(sessionId, result.speakLines);
63
- deliverMessages(sessionId, result.messages);
66
+ deliverSpokenThenPlay(sessionId, result);
64
67
 
65
68
  if (result.pendingWeather) {
66
69
  const weatherResult = await resolveWeatherTurn(
@@ -69,8 +72,7 @@ async function applyTurn(
69
72
  result.pendingWeather.country,
70
73
  );
71
74
  sessions.set(sessionId, weatherResult.state);
72
- speakLines(sessionId, weatherResult.speakLines);
73
- deliverMessages(sessionId, weatherResult.messages);
75
+ deliverSpokenThenPlay(sessionId, weatherResult);
74
76
  }
75
77
  }
76
78
 
@@ -83,13 +85,10 @@ async function onUserText(sessionId: string, text: string): Promise<void> {
83
85
  defineAgent({
84
86
  onSessionStart({ sessionId }) {
85
87
  sessions.set(sessionId, createInitialState());
86
- sendToClient(sessionId, {
87
- type: "agent_event",
88
- event: "session_start",
89
- sessionId,
88
+ applyOutboundOps(sessionId, greetingOps(sessionId, GREETING), {
89
+ sendToClient,
90
+ speak,
90
91
  });
91
- speak(sessionId, GREETING);
92
- sendToClient(sessionId, { type: "chat_reply", text: GREETING });
93
92
  agentLog("info", `voice-showcase session_start ${sessionId}`);
94
93
  },
95
94
 
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Voice-showcase outbound order: send spoken TTS text to the client first,
3
+ * then trigger TTS play. Parent IPC preserves this order.
4
+ */
5
+
6
+ import type { OutboundMessage } from "./conversation.js";
7
+
8
+ export type OutboundOp =
9
+ | { kind: "send"; message: OutboundMessage }
10
+ | { kind: "play"; text: string };
11
+
12
+ export interface OutboundDeps {
13
+ sendToClient: (sessionId: string, payload: unknown) => void;
14
+ speak: (sessionId: string, text: string) => void;
15
+ }
16
+
17
+ /** All DataChannel payloads, then TTS play commands. */
18
+ export function spokenThenPlayOps(
19
+ messages: OutboundMessage[],
20
+ speakLines: string[],
21
+ ): OutboundOp[] {
22
+ const ops: OutboundOp[] = [];
23
+ for (const message of messages) {
24
+ ops.push({ kind: "send", message });
25
+ }
26
+ for (const text of speakLines) {
27
+ ops.push({ kind: "play", text });
28
+ }
29
+ return ops;
30
+ }
31
+
32
+ export function greetingOps(
33
+ sessionId: string,
34
+ greeting: string,
35
+ ): OutboundOp[] {
36
+ return spokenThenPlayOps(
37
+ [
38
+ { type: "agent_event", event: "session_start", sessionId },
39
+ { type: "chat_reply", text: greeting },
40
+ ],
41
+ [greeting],
42
+ );
43
+ }
44
+
45
+ export function applyOutboundOps(
46
+ sessionId: string,
47
+ ops: OutboundOp[],
48
+ deps: OutboundDeps,
49
+ ): void {
50
+ for (const op of ops) {
51
+ if (op.kind === "send") {
52
+ deps.sendToClient(sessionId, op.message);
53
+ } else {
54
+ deps.speak(sessionId, op.text);
55
+ }
56
+ }
57
+ }