@pagelines/sdk 1.0.823 → 1.0.825

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.
@@ -13,14 +13,25 @@ export interface VoiceSessionError {
13
13
  message: string;
14
14
  }
15
15
  export type VoiceDisconnectCause = 'agent' | 'error' | 'network' | 'unknown';
16
+ /**
17
+ * Who the transport says is talking. Raw energy is not a speech signal — room
18
+ * tone and the speaker's own playback both read as audible — so ducking,
19
+ * tone, and which meter the surface follows come from here.
20
+ */
21
+ export interface VoiceSpeakingState {
22
+ user: boolean;
23
+ agent: boolean;
24
+ }
16
25
  export interface VoiceProviderClient {
17
26
  connect: (handlers: {
18
27
  onDisconnected: (cause: VoiceDisconnectCause) => void;
19
28
  onError: (error: VoiceSessionError) => void;
20
29
  onThinkingChange?: (thinking: boolean) => void;
30
+ onSpeakingChange?: (state: VoiceSpeakingState) => void;
21
31
  }) => Promise<void>;
22
32
  disconnect: () => Promise<void>;
23
- sampleLevel: () => VoiceLevelEvent | undefined;
33
+ /** Loudest source by default; `prefer` pins a meter while someone is known to be speaking. */
34
+ sampleLevel: (prefer?: VoiceLevelEvent['source']) => VoiceLevelEvent | undefined;
24
35
  /** Self-mute without ending the live session. */
25
36
  setMuted?: (muted: boolean) => Promise<void>;
26
37
  /**
@@ -1,5 +1,5 @@
1
1
  import { VoiceSessionMint } from '@pagelines/core';
2
- import { VoiceProviderClient } from '../../provider';
2
+ import { VoiceLevelEvent, VoiceProviderClient } from '../../provider';
3
3
  type LiveKitMint = Extract<VoiceSessionMint, {
4
4
  provider: 'livekit';
5
5
  }>;
@@ -40,6 +40,10 @@ export declare class LiveKitVoiceProvider implements VoiceProviderClient {
40
40
  private levelSources;
41
41
  private attachedElements;
42
42
  private closing;
43
+ private userSpeaking;
44
+ private agentActiveSpeaker;
45
+ private agentStateSpeaking;
46
+ private lastSpeaking?;
43
47
  constructor(mint: LiveKitMint, deps?: {
44
48
  createRoom?: () => VoiceRoom;
45
49
  createAnalyser?: (track: unknown) => VoiceLevelAnalyser;
@@ -47,7 +51,8 @@ export declare class LiveKitVoiceProvider implements VoiceProviderClient {
47
51
  connect(handlers: Parameters<VoiceProviderClient['connect']>[0]): Promise<void>;
48
52
  disconnect(): Promise<void>;
49
53
  setMuted(muted: boolean): Promise<void>;
50
- sampleLevel(): ReturnType<VoiceProviderClient['sampleLevel']>;
54
+ sampleLevel(prefer?: VoiceLevelEvent['source']): ReturnType<VoiceProviderClient['sampleLevel']>;
55
+ private publishSpeaking;
51
56
  private waitForAgentReady;
52
57
  private analyserFor;
53
58
  private watchMicLevel;
@@ -1,7 +1,7 @@
1
1
  import { Ref } from 'vue';
2
- import { CreateVoiceProviderClient, VoiceLevelEvent, VoiceSessionError } from './provider';
2
+ import { CreateVoiceProviderClient, VoiceLevelEvent, VoiceSessionError, VoiceSpeakingState } from './provider';
3
3
  import { SettingsObject } from '@pagelines/core';
4
- export type { VoiceLevelEvent, VoiceSessionError } from './provider';
4
+ export type { VoiceLevelEvent, VoiceSessionError, VoiceSpeakingState } from './provider';
5
5
  export { isVoiceAudible, VOICE_SPECTRUM_BANDS, voiceSpectrum } from './provider';
6
6
  export type VoiceSessionClientState = 'idle' | 'minting' | 'connecting' | 'live' | 'ended';
7
7
  export type VoiceChimeKind = 'start' | 'end';
@@ -20,6 +20,7 @@ export interface VoiceClientHandlers {
20
20
  onLevel?: (event: VoiceLevelEvent) => void;
21
21
  onError?: (error: VoiceSessionError) => void;
22
22
  onThinkingChange?: (thinking: boolean) => void;
23
+ onSpeakingChange?: (state: VoiceSpeakingState) => void;
23
24
  /** Fire-and-forget latency phases for product observability. */
24
25
  onMetric?: (metric: VoiceMetric) => void;
25
26
  onWorkChange?: (work: VoiceWorkStatus) => void;
@@ -62,7 +63,7 @@ export declare class VoiceSessionClient extends SettingsObject<VoiceSessionClien
62
63
  private workAbort?;
63
64
  private workState;
64
65
  private workCuePlaying;
65
- private audible;
66
+ private speaking;
66
67
  constructor(settings: VoiceSessionClientSettings);
67
68
  start(): Promise<void>;
68
69
  end(): Promise<void>;
@@ -79,6 +80,8 @@ export declare class VoiceSessionClient extends SettingsObject<VoiceSessionClien
79
80
  private startLevelTimer;
80
81
  private authHeaders;
81
82
  private startWorkStream;
83
+ private setSpeaking;
84
+ /** Duck on speech, not on energy: room tone kept the loop from ever starting. */
82
85
  private updateWorkCue;
83
86
  private setWorkCue;
84
87
  private playChime;
package/dist/agent.js CHANGED
@@ -1,15 +1,18 @@
1
1
  import "./rolldown-runtime.js";
2
- import { _ as e, a as t, c as n, g as r, n as i, r as a, t as o, v as s } from "./AgentWrap.js";
3
- import { n as c, t as l } from "./AgentProvider.js";
4
- import { Fragment as u, createBlock as d, createCommentVNode as f, createElementBlock as p, createElementVNode as m, createVNode as h, defineComponent as g, nextTick as _, normalizeClass as v, normalizeStyle as y, openBlock as b, ref as x, renderList as S, watch as C, withCtx as w } from "vue";
2
+ import { c as e, s as t } from "./sdkClient.js";
3
+ import { a as n, h as r, m as i, n as a, p as o, r as s, t as c } from "./AgentWrap.js";
4
+ import { n as l, t as u } from "./AgentProvider.js";
5
+ import { n as d, t as f } from "./provider.js";
6
+ import { SettingsObject as p, readChatEventStream as m, zVoiceSessionMint as h } from "@pagelines/core";
7
+ import { Fragment as g, createBlock as _, createCommentVNode as v, createElementBlock as y, createElementVNode as b, createVNode as x, defineComponent as S, nextTick as C, normalizeClass as w, normalizeStyle as T, openBlock as E, ref as D, renderList as O, watch as k, withCtx as A } from "vue";
5
8
  //#region agent/ui/AgentWidget.vue?vue&type=script&setup=true&lang.ts
6
- var T = {
9
+ var j = {
7
10
  key: 0,
8
11
  class: "relative h-full w-full"
9
- }, E = { class: "group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100" }, D = ["src", "alt"], O = {
12
+ }, M = { class: "group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100" }, N = ["src", "alt"], P = {
10
13
  key: 0,
11
14
  class: "absolute top-0.5 right-0.5"
12
- }, k = /* @__PURE__ */ g({
15
+ }, F = /* @__PURE__ */ S({
13
16
  __name: "AgentWidget",
14
17
  props: {
15
18
  sdk: {},
@@ -19,12 +22,12 @@ var T = {
19
22
  position: {}
20
23
  },
21
24
  setup(e, { expose: t }) {
22
- let n = e, r = x(!1), i = x(!1);
25
+ let n = e, r = D(!1), i = D(!1);
23
26
  function toggle() {
24
27
  r.value = !r.value;
25
28
  }
26
- C(r, async (e) => {
27
- e ? (await _(), setTimeout(() => {
29
+ k(r, async (e) => {
30
+ e ? (await C(), setTimeout(() => {
28
31
  i.value = !0;
29
32
  }, 400)) : i.value = !1;
30
33
  });
@@ -41,29 +44,29 @@ var T = {
41
44
  toggle,
42
45
  open,
43
46
  close
44
- }), (t, a) => (b(), d(o, {
47
+ }), (t, a) => (E(), _(c, {
45
48
  sdk: e.sdk,
46
49
  handle: e.handle,
47
50
  context: e.context,
48
51
  "first-message": e.firstMessage
49
52
  }, {
50
- default: w(({ agent: t, sdk: o }) => [t ? (b(), p("div", {
53
+ default: A(({ agent: t, sdk: o }) => [t ? (E(), y("div", {
51
54
  key: 0,
52
- class: v(["fixed z-[999999] pointer-events-auto", [n.position === "bottom-left" ? "bottom-6 left-6" : n.position === "top-right" ? "top-6 right-6" : n.position === "top-left" ? "top-6 left-6" : "bottom-6 right-6"]])
53
- }, [m("div", {
54
- class: v(["transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu", [r.value ? "widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100" : "widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer"]]),
55
- style: y({ "transform-origin": n.position === "bottom-left" ? "bottom left" : n.position === "top-right" ? "top right" : n.position === "top-left" ? "top left" : "bottom right" }),
55
+ class: w(["fixed z-[999999] pointer-events-auto", [n.position === "bottom-left" ? "bottom-6 left-6" : n.position === "top-right" ? "top-6 right-6" : n.position === "top-left" ? "top-6 left-6" : "bottom-6 right-6"]])
56
+ }, [b("div", {
57
+ class: w(["transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu", [r.value ? "widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100" : "widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer"]]),
58
+ style: T({ "transform-origin": n.position === "bottom-left" ? "bottom left" : n.position === "top-right" ? "top right" : n.position === "top-left" ? "top left" : "bottom right" }),
56
59
  onClick: a[0] || (a[0] = (e) => r.value ? void 0 : toggle())
57
- }, [r.value ? f("", !0) : (b(), p("div", T, [m("div", E, [m("img", {
60
+ }, [r.value ? v("", !0) : (E(), y("div", j, [b("div", M, [b("img", {
58
61
  src: t.avatarUrl.value,
59
62
  alt: `${t.displayName.value} assistant`,
60
63
  class: "h-full w-full object-cover pointer-events-none",
61
64
  width: "80",
62
65
  height: "80"
63
- }, null, 8, D), a[1] || (a[1] = m("div", { class: "absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40" }, null, -1))]), t.state.value.lifecycle === "running" ? (b(), p("div", O, [...a[2] || (a[2] = [m("div", {
66
+ }, null, 8, N), a[1] || (a[1] = b("div", { class: "absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40" }, null, -1))]), t.state.value.lifecycle === "running" ? (E(), y("div", P, [...a[2] || (a[2] = [b("div", {
64
67
  class: "size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping",
65
68
  style: { "animation-duration": "3s" }
66
- }, null, -1), m("div", { class: "size-3 bg-green-500 rounded-full ring-2 ring-white" }, null, -1)])])) : f("", !0)])), r.value && t ? (b(), d(c, {
69
+ }, null, -1), b("div", { class: "size-3 bg-green-500 rounded-full ring-2 ring-white" }, null, -1)])])) : v("", !0)])), r.value && t ? (E(), _(l, {
67
70
  key: 1,
68
71
  class: "rounded-3xl shadow-2xl border border-white/10",
69
72
  sdk: o,
@@ -79,7 +82,7 @@ var T = {
79
82
  "context",
80
83
  "first-message",
81
84
  "is-active"
82
- ])) : f("", !0)], 6)], 2)) : f("", !0)]),
85
+ ])) : v("", !0)], 6)], 2)) : v("", !0)]),
83
86
  _: 1
84
87
  }, 8, [
85
88
  "sdk",
@@ -88,28 +91,307 @@ var T = {
88
91
  "first-message"
89
92
  ]));
90
93
  }
91
- }), A = ["data-sender"], j = /* @__PURE__ */ g({
94
+ }), I = ["data-sender"], L = /* @__PURE__ */ S({
92
95
  __name: "ElAgentChatPreview",
93
96
  props: {
94
97
  messages: {},
95
98
  variant: {}
96
99
  },
97
100
  setup(e) {
98
- return (t, r) => (b(), p("div", {
99
- class: v(["@container/chat-preview flex flex-col gap-3", e.variant === "dark" ? "text-white" : "text-slate-950"]),
101
+ return (t, r) => (E(), y("div", {
102
+ class: w(["@container/chat-preview flex flex-col gap-3", e.variant === "dark" ? "text-white" : "text-slate-950"]),
100
103
  "data-test": "agent-chat-preview"
101
- }, [(b(!0), p(u, null, S(e.messages, (t, r) => (b(), p("div", {
104
+ }, [(E(!0), y(g, null, O(e.messages, (t, r) => (E(), y("div", {
102
105
  key: `${t.sender}-${r}`,
103
- class: v(["flex", t.sender === "user" ? "justify-end" : "justify-start"]),
106
+ class: w(["flex", t.sender === "user" ? "justify-end" : "justify-start"]),
104
107
  "data-test": "agent-chat-preview-message",
105
108
  "data-sender": t.sender
106
- }, [m("div", { class: v(["max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]", t.sender === "user" ? "bg-slate-950 text-white" : e.variant === "dark" ? "border border-white/10 bg-white/10 text-white" : "border border-slate-200 bg-white text-slate-950"]) }, [h(n, {
109
+ }, [b("div", { class: w(["max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]", t.sender === "user" ? "bg-slate-950 text-white" : e.variant === "dark" ? "border border-white/10 bg-white/10 text-white" : "border border-slate-200 bg-white text-slate-950"]) }, [x(n, {
107
110
  text: t.text,
108
111
  inverted: t.sender === "user" || e.variant === "dark"
109
- }, null, 8, ["text", "inverted"])], 2)], 10, A))), 128))], 2));
112
+ }, null, 8, ["text", "inverted"])], 2)], 10, I))), 128))], 2));
110
113
  }
111
114
  });
112
115
  //#endregion
113
- export { e as AGENT_MODES, s as Agent, c as AgentChat, r as AgentChatController, r as AgentController, l as AgentProvider, k as AgentWidget, o as AgentWrap, i as ElAgentChat, j as ElAgentChatPreview, a as VoiceMode, t as VoiceSessionClient };
116
+ //#region agent/voice/voice-work-state.ts
117
+ function createVoiceWorkState() {
118
+ return { turnIds: /* @__PURE__ */ new Set() };
119
+ }
120
+ function reduceVoiceWorkEvent(e, t) {
121
+ if (t.event === "message") {
122
+ let n = t.data.message;
123
+ if (n.role === "user" && n.metadata?.clientContext === "voice") return {
124
+ ...e,
125
+ turnIds: new Set(e.turnIds).add(n.messageId)
126
+ };
127
+ let r = typeof n.metadata?.turnId == "string" ? n.metadata.turnId : void 0;
128
+ if (r && e.turnIds.has(r)) {
129
+ let t = new Set(e.turnIds);
130
+ return t.delete(r), e.activeTurnId === r ? { turnIds: t } : {
131
+ ...e,
132
+ turnIds: t
133
+ };
134
+ }
135
+ return e;
136
+ }
137
+ let n = "turnId" in t.data && typeof t.data.turnId == "string" ? t.data.turnId : void 0;
138
+ return !n || !e.turnIds.has(n) ? e : t.event === "working_state" ? {
139
+ ...e,
140
+ activeTurnId: n,
141
+ description: t.data.description.trim() || "Working on it"
142
+ } : t.event === "tool_activity" && t.data.status === "started" ? {
143
+ ...e,
144
+ activeTurnId: n,
145
+ description: e.activeTurnId === n && e.description ? e.description : t.data.label
146
+ } : t.event === "working_end" || t.event === "stream_end" ? {
147
+ ...e,
148
+ activeTurnId: void 0,
149
+ description: void 0
150
+ } : e;
151
+ }
152
+ //#endregion
153
+ //#region agent/voice/voice-session-client.ts
154
+ var R = {
155
+ start: "/audio/start-call.mp3",
156
+ end: "/audio/end-call.mp3"
157
+ }, z = .7, B = 50, V = 15e3;
158
+ function createBrowserVoiceChimePlayer(e) {
159
+ let t = Object.fromEntries(Object.entries(R).map(([t, n]) => {
160
+ let r = new Audio(`${e}${n}`);
161
+ return r.preload = "auto", r.volume = z, [t, r];
162
+ }));
163
+ return (e) => {
164
+ t[e].currentTime = 0, t[e].play().catch(() => {});
165
+ };
166
+ }
167
+ function createBrowserVoiceWorkCue(e) {
168
+ let t = new Audio(`${e}/audio/thinking.mp3`);
169
+ return t.preload = "auto", t.loop = !0, t.volume = .16, (e) => {
170
+ if (e) {
171
+ t.play().catch(() => {});
172
+ return;
173
+ }
174
+ t.pause(), t.currentTime = 0;
175
+ };
176
+ }
177
+ var VoiceSessionClient = class extends p {
178
+ constructor(e) {
179
+ super("VoiceSessionClient", e), t(this, "state", D("idle")), t(this, "isMuted", D(!1)), t(this, "provider", void 0), t(this, "chime", void 0), t(this, "workCue", void 0), t(this, "generation", 0), t(this, "levelTimer", void 0), t(this, "startAtMs", 0), t(this, "liveAtMs", 0), t(this, "firstPlaybackEmitted", !1), t(this, "mutePending", !1), t(this, "conversationId", void 0), t(this, "workAbort", void 0), t(this, "workState", createVoiceWorkState()), t(this, "workCuePlaying", !1), t(this, "speaking", {
180
+ user: !1,
181
+ agent: !1
182
+ }), this.chime = e.playChime, this.workCue = e.setWorkCue;
183
+ }
184
+ async start() {
185
+ if (this.state.value !== "idle") return;
186
+ let e = ++this.generation;
187
+ this.startAtMs = Date.now(), this.liveAtMs = 0, this.firstPlaybackEmitted = !1, this.workState = createVoiceWorkState(), this.setState("minting");
188
+ try {
189
+ let t = await this.mintSession(e);
190
+ if (!t || e !== this.generation) return;
191
+ this.conversationId && this.startWorkStream(this.conversationId, e), this.setState("connecting"), await this.connect({
192
+ mint: t,
193
+ generation: e
194
+ });
195
+ } catch (t) {
196
+ if (e !== this.generation) return;
197
+ this.fail(this.toSessionError(t, "VOICE_MINT_FAILED"));
198
+ }
199
+ }
200
+ async end() {
201
+ if (this.state.value === "idle" || this.state.value === "ended") return;
202
+ let e = this.state.value === "live";
203
+ this.generation++, e && this.playChime("end"), this.setState("ended"), await this.teardown();
204
+ }
205
+ async connect(e) {
206
+ let { mint: t, generation: n } = e, r = await (this.settings.createProvider ?? f)(t);
207
+ if (n !== this.generation) {
208
+ await r.disconnect().catch(() => {});
209
+ return;
210
+ }
211
+ this.provider = r;
212
+ let i = r.connect({
213
+ onDisconnected: (e) => {
214
+ if (!(n !== this.generation || this.provider !== r)) {
215
+ if (this.state.value === "live" && e === "agent") {
216
+ this.generation++, this.playChime("end"), this.setState("ended"), this.teardown();
217
+ return;
218
+ }
219
+ this.fail({
220
+ code: this.state.value === "live" ? "VOICE_CONNECTION_LOST" : "VOICE_CONNECT_FAILED",
221
+ message: this.state.value === "live" ? "The voice connection dropped." : "Could not open the voice connection."
222
+ });
223
+ }
224
+ },
225
+ onError: (e) => {
226
+ n === this.generation && this.provider === r && this.fail(e);
227
+ },
228
+ onThinkingChange: (e) => {
229
+ n === this.generation && this.provider === r && this.settings.onThinkingChange?.(e);
230
+ },
231
+ onSpeakingChange: (e) => {
232
+ n === this.generation && this.provider === r && this.setSpeaking(e);
233
+ }
234
+ }), a;
235
+ try {
236
+ await Promise.race([i, new Promise((e, t) => {
237
+ a = setTimeout(() => t(Object.assign(/* @__PURE__ */ Error("Voice connection timed out."), { code: "VOICE_CONNECT_TIMEOUT" })), V);
238
+ })]);
239
+ } catch (e) {
240
+ if (i.then(() => r.disconnect()).catch(() => {}), n !== this.generation || this.provider !== r) return;
241
+ this.fail(this.toSessionError(e, "VOICE_CONNECT_FAILED"));
242
+ return;
243
+ } finally {
244
+ a && clearTimeout(a);
245
+ }
246
+ n !== this.generation || this.provider !== r || (this.startLevelTimer(r, n), this.liveAtMs = Date.now(), this.setState("live"), this.updateWorkCue(), this.emitMetric("voice.ready", this.liveAtMs - this.startAtMs), this.playChime("start"));
247
+ }
248
+ get canSendUserMessage() {
249
+ return this.state.value === "live" && !!this.provider?.sendUserMessage;
250
+ }
251
+ sendUserMessage(e) {
252
+ let t = e.trim();
253
+ return !t || !this.canSendUserMessage ? !1 : (this.provider.sendUserMessage(t), !0);
254
+ }
255
+ async toggleMuted() {
256
+ let e = this.provider;
257
+ if (this.state.value !== "live" || !e?.setMuted || this.mutePending) return;
258
+ let t = this.generation, n = !this.isMuted.value;
259
+ this.mutePending = !0;
260
+ try {
261
+ return await e.setMuted(n), t !== this.generation || this.provider !== e || this.state.value !== "live" ? void 0 : (this.isMuted.value = n, n && this.speaking.user && this.setSpeaking({
262
+ ...this.speaking,
263
+ user: !1
264
+ }), n);
265
+ } catch {
266
+ return;
267
+ } finally {
268
+ this.mutePending = !1;
269
+ }
270
+ }
271
+ emitMetric(e, t) {
272
+ this.settings.onMetric?.({
273
+ name: e,
274
+ durationMs: Math.max(0, Math.round(t))
275
+ });
276
+ }
277
+ setState(e) {
278
+ this.state.value !== e && (this.state.value = e, this.settings.onStateChange?.(e));
279
+ }
280
+ fail(e) {
281
+ let t = this.state.value === "live";
282
+ this.generation++, t && this.playChime("end"), this.settings.onError?.(e), this.setState("ended"), this.teardown();
283
+ }
284
+ async teardown() {
285
+ this.workAbort?.abort(), this.workAbort = void 0;
286
+ let e = !!this.workState.activeTurnId;
287
+ this.workState = createVoiceWorkState(), e && this.settings.onWorkChange?.({ active: !1 }), this.setSpeaking({
288
+ user: !1,
289
+ agent: !1
290
+ }), this.settings.onThinkingChange?.(!1), this.setWorkCue(!1), this.levelTimer !== void 0 && (clearInterval(this.levelTimer), this.levelTimer = void 0);
291
+ let t = this.provider;
292
+ this.provider = void 0, this.isMuted.value = !1, this.settings.onLevel?.({
293
+ source: "playback",
294
+ level: 0
295
+ }), t && await t.disconnect().catch(() => {});
296
+ }
297
+ startLevelTimer(e, t) {
298
+ this.levelTimer === void 0 && (this.levelTimer = setInterval(() => {
299
+ if (t !== this.generation || this.provider !== e) return;
300
+ let n = e.sampleLevel(this.speaking.agent ? "playback" : this.speaking.user ? "mic" : void 0);
301
+ n && (this.settings.onLevel?.(n), !this.firstPlaybackEmitted && n.source === "playback" && d(n.level) && this.liveAtMs > 0 && (this.firstPlaybackEmitted = !0, this.emitMetric("voice.first_playback", Date.now() - this.liveAtMs)));
302
+ }, B));
303
+ }
304
+ authHeaders() {
305
+ return this.settings.token ? { Authorization: `Bearer ${this.settings.token}` } : {};
306
+ }
307
+ startWorkStream(t, n) {
308
+ this.workAbort?.abort();
309
+ let r = new AbortController();
310
+ this.workAbort = r;
311
+ let i = e(this.settings.apiBase, this.settings.isDev), a = this.settings.fetchFn ?? fetch, o = this.settings.latestSequence ? `${i}/api/conversations/${encodeURIComponent(t)}/stream?since=${encodeURIComponent(this.settings.latestSequence)}` : `${i}/api/conversations/${encodeURIComponent(t)}/stream`;
312
+ (async () => {
313
+ let e = await a(o, {
314
+ headers: {
315
+ Accept: "text/event-stream",
316
+ ...this.authHeaders()
317
+ },
318
+ credentials: "include",
319
+ signal: r.signal
320
+ });
321
+ if (!(!e.ok || !e.body)) for await (let t of m(e.body.getReader())) {
322
+ if (r.signal.aborted || n !== this.generation) return;
323
+ let e = this.workState, i = reduceVoiceWorkEvent(e, t);
324
+ i !== e && (this.workState = i, (i.activeTurnId !== e.activeTurnId || i.description !== e.description) && (this.settings.onWorkChange?.({
325
+ active: !!i.activeTurnId,
326
+ ...i.description ? { description: i.description } : {}
327
+ }), this.updateWorkCue()));
328
+ }
329
+ })().catch(() => {});
330
+ }
331
+ setSpeaking(e) {
332
+ let t = {
333
+ user: e.user && !this.isMuted.value,
334
+ agent: e.agent
335
+ };
336
+ t.user === this.speaking.user && t.agent === this.speaking.agent || (this.speaking = t, this.settings.onSpeakingChange?.(t), this.updateWorkCue());
337
+ }
338
+ updateWorkCue() {
339
+ this.setWorkCue(this.state.value === "live" && !!this.workState.activeTurnId && !this.speaking.user && !this.speaking.agent);
340
+ }
341
+ setWorkCue(t) {
342
+ this.workCuePlaying !== t && (this.workCuePlaying = t, this.workCue ?? (this.workCue = createBrowserVoiceWorkCue(e(this.settings.apiBase, this.settings.isDev))), this.workCue(t));
343
+ }
344
+ playChime(t) {
345
+ this.chime ?? (this.chime = createBrowserVoiceChimePlayer(e(this.settings.apiBase, this.settings.isDev))), this.chime(t);
346
+ }
347
+ async post(t) {
348
+ let n = e(this.settings.apiBase, this.settings.isDev), r = await (this.settings.fetchFn ?? fetch)(`${n}${t.path}`, {
349
+ method: "POST",
350
+ headers: {
351
+ "Content-Type": "application/json",
352
+ ...this.authHeaders()
353
+ },
354
+ credentials: "include",
355
+ body: JSON.stringify(t.body)
356
+ }), i = await r.json();
357
+ if (!r.ok || !i.ok) {
358
+ let e = i.ok ? `HTTP ${r.status}` : i.error, t = !i.ok && i.code ? i.code : "VOICE_MINT_FAILED";
359
+ throw Object.assign(Error(e || "Voice request failed."), { code: t });
360
+ }
361
+ return i.data;
362
+ }
363
+ async mintSession(e) {
364
+ let t = this.settings.conversationId;
365
+ if (!t && (t = (await this.post({
366
+ path: "/api/conversations",
367
+ body: {
368
+ orgId: this.settings.orgId,
369
+ participants: [{
370
+ type: "agent",
371
+ id: this.settings.agentId
372
+ }]
373
+ }
374
+ })).conversationId, e !== this.generation)) return null;
375
+ let n = await this.post({
376
+ path: "/api/voice/sessions",
377
+ body: { conversationId: t }
378
+ });
379
+ if (e !== this.generation) return null;
380
+ let r = h.safeParse(n);
381
+ if (!r.success) throw Object.assign(/* @__PURE__ */ Error("Voice session mint returned an unexpected shape."), { code: "VOICE_MINT_INVALID" });
382
+ return this.conversationId = t, r.data;
383
+ }
384
+ toSessionError(e, t) {
385
+ return e instanceof Error ? {
386
+ code: "code" in e ? String(e.code) : t,
387
+ message: e.message
388
+ } : {
389
+ code: t,
390
+ message: "Voice session failed to start."
391
+ };
392
+ }
393
+ };
394
+ //#endregion
395
+ export { i as AGENT_MODES, r as Agent, l as AgentChat, o as AgentChatController, o as AgentController, u as AgentProvider, F as AgentWidget, c as AgentWrap, a as ElAgentChat, L as ElAgentChatPreview, s as VoiceMode, VoiceSessionClient };
114
396
 
115
397
  //# sourceMappingURL=agent.js.map
package/dist/agent.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","names":[],"sources":["../agent/ui/AgentWidget.vue","../agent/ui/AgentWidget.vue","../agent/ui/ElAgentChatPreview.vue","../agent/ui/ElAgentChatPreview.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { PageLinesSDK } from '../../sdkClient'\nimport { nextTick, ref, watch } from 'vue'\nimport AgentChat from './AgentChat.vue'\nimport AgentWrap from './AgentWrap.vue'\n\ninterface Props {\n sdk?: PageLinesSDK\n handle: string\n context?: string\n firstMessage?: string\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n}\n\nconst props = defineProps<Props>()\nconst isOpen = ref(false)\nconst isAgentActive = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\n// Watch isOpen to delay agent activation for smooth transition\nwatch(isOpen, async (newValue) => {\n if (newValue) {\n // When opening, delay agent activation until transition starts\n await nextTick()\n setTimeout(() => {\n isAgentActive.value = true\n }, 400) // Delay to let container expand first\n } else {\n // When closing, immediately deactivate agent\n isAgentActive.value = false\n }\n})\n\nfunction open() {\n isOpen.value = true\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleClose() {\n close()\n}\n\ndefineExpose({\n toggle,\n open,\n close,\n})\n</script>\n\n<template>\n <AgentWrap\n v-slot=\"{ agent, sdk: resolvedSdk }\"\n :sdk=\"sdk\"\n :handle=\"handle\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n >\n <!-- Fixed Widget Wrapper -->\n <div\n v-if=\"agent\"\n class=\"fixed z-[999999] pointer-events-auto\"\n :class=\"[\n props.position === 'bottom-left' ? 'bottom-6 left-6'\n : props.position === 'top-right' ? 'top-6 right-6'\n : props.position === 'top-left' ? 'top-6 left-6'\n : 'bottom-6 right-6',\n ]\"\n >\n <!-- Widget Content with Transition -->\n <div\n class=\"transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu\"\n :class=\"[\n isOpen\n ? 'widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100'\n : 'widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer',\n ]\"\n :style=\"{\n 'transform-origin':\n props.position === 'bottom-left' ? 'bottom left'\n : props.position === 'top-right' ? 'top right'\n : props.position === 'top-left' ? 'top left'\n : 'bottom right',\n }\"\n @click=\"!isOpen ? toggle() : undefined\"\n >\n <!-- Button Content (visible when closed) -->\n <div v-if=\"!isOpen\" class=\"relative h-full w-full\">\n <div class=\"group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100\">\n <img\n :src=\"agent.avatarUrl.value\"\n :alt=\"`${agent.displayName.value} assistant`\"\n class=\"h-full w-full object-cover pointer-events-none\"\n width=\"80\"\n height=\"80\"\n />\n <!-- Gradient overlay for contrast -->\n <div class=\"absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40\" />\n </div>\n <!-- Online indicator -->\n <div v-if=\"agent.state.value.lifecycle === 'running'\" class=\"absolute top-0.5 right-0.5\">\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping\" style=\"animation-duration: 3s;\" />\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white\" />\n </div>\n </div>\n\n <!-- Chat Content (visible when open) -->\n <AgentChat\n v-if=\"isOpen && agent\"\n class=\"rounded-3xl shadow-2xl border border-white/10\"\n :sdk=\"resolvedSdk\"\n :agent=\"agent\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n :has-close=\"true\"\n :is-active=\"isAgentActive\"\n @close=\"handleClose\"\n />\n </div>\n </div>\n </AgentWrap>\n</template>\n","<script setup lang=\"ts\">\nimport type { PageLinesSDK } from '../../sdkClient'\nimport { nextTick, ref, watch } from 'vue'\nimport AgentChat from './AgentChat.vue'\nimport AgentWrap from './AgentWrap.vue'\n\ninterface Props {\n sdk?: PageLinesSDK\n handle: string\n context?: string\n firstMessage?: string\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n}\n\nconst props = defineProps<Props>()\nconst isOpen = ref(false)\nconst isAgentActive = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\n// Watch isOpen to delay agent activation for smooth transition\nwatch(isOpen, async (newValue) => {\n if (newValue) {\n // When opening, delay agent activation until transition starts\n await nextTick()\n setTimeout(() => {\n isAgentActive.value = true\n }, 400) // Delay to let container expand first\n } else {\n // When closing, immediately deactivate agent\n isAgentActive.value = false\n }\n})\n\nfunction open() {\n isOpen.value = true\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleClose() {\n close()\n}\n\ndefineExpose({\n toggle,\n open,\n close,\n})\n</script>\n\n<template>\n <AgentWrap\n v-slot=\"{ agent, sdk: resolvedSdk }\"\n :sdk=\"sdk\"\n :handle=\"handle\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n >\n <!-- Fixed Widget Wrapper -->\n <div\n v-if=\"agent\"\n class=\"fixed z-[999999] pointer-events-auto\"\n :class=\"[\n props.position === 'bottom-left' ? 'bottom-6 left-6'\n : props.position === 'top-right' ? 'top-6 right-6'\n : props.position === 'top-left' ? 'top-6 left-6'\n : 'bottom-6 right-6',\n ]\"\n >\n <!-- Widget Content with Transition -->\n <div\n class=\"transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu\"\n :class=\"[\n isOpen\n ? 'widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100'\n : 'widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer',\n ]\"\n :style=\"{\n 'transform-origin':\n props.position === 'bottom-left' ? 'bottom left'\n : props.position === 'top-right' ? 'top right'\n : props.position === 'top-left' ? 'top left'\n : 'bottom right',\n }\"\n @click=\"!isOpen ? toggle() : undefined\"\n >\n <!-- Button Content (visible when closed) -->\n <div v-if=\"!isOpen\" class=\"relative h-full w-full\">\n <div class=\"group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100\">\n <img\n :src=\"agent.avatarUrl.value\"\n :alt=\"`${agent.displayName.value} assistant`\"\n class=\"h-full w-full object-cover pointer-events-none\"\n width=\"80\"\n height=\"80\"\n />\n <!-- Gradient overlay for contrast -->\n <div class=\"absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40\" />\n </div>\n <!-- Online indicator -->\n <div v-if=\"agent.state.value.lifecycle === 'running'\" class=\"absolute top-0.5 right-0.5\">\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping\" style=\"animation-duration: 3s;\" />\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white\" />\n </div>\n </div>\n\n <!-- Chat Content (visible when open) -->\n <AgentChat\n v-if=\"isOpen && agent\"\n class=\"rounded-3xl shadow-2xl border border-white/10\"\n :sdk=\"resolvedSdk\"\n :agent=\"agent\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n :has-close=\"true\"\n :is-active=\"isAgentActive\"\n @close=\"handleClose\"\n />\n </div>\n </div>\n </AgentWrap>\n</template>\n","<script setup lang=\"ts\">\nimport ChatRichText from './ChatRichText.vue'\n\nexport type ChatPreviewMessage = {\n sender: 'user' | 'agent'\n text: string\n}\n\ndefineProps<{\n messages: ChatPreviewMessage[]\n variant?: 'light' | 'dark'\n}>()\n</script>\n\n<template>\n <div\n class=\"@container/chat-preview flex flex-col gap-3\"\n :class=\"variant === 'dark' ? 'text-white' : 'text-slate-950'\"\n data-test=\"agent-chat-preview\"\n >\n <div\n v-for=\"(message, index) in messages\"\n :key=\"`${message.sender}-${index}`\"\n class=\"flex\"\n :class=\"message.sender === 'user' ? 'justify-end' : 'justify-start'\"\n data-test=\"agent-chat-preview-message\"\n :data-sender=\"message.sender\"\n >\n <div\n class=\"max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]\"\n :class=\"message.sender === 'user'\n ? 'bg-slate-950 text-white'\n : variant === 'dark'\n ? 'border border-white/10 bg-white/10 text-white'\n : 'border border-slate-200 bg-white text-slate-950'\"\n >\n <ChatRichText\n :text=\"message.text\"\n :inverted=\"message.sender === 'user' || variant === 'dark'\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ChatRichText from './ChatRichText.vue'\n\nexport type ChatPreviewMessage = {\n sender: 'user' | 'agent'\n text: string\n}\n\ndefineProps<{\n messages: ChatPreviewMessage[]\n variant?: 'light' | 'dark'\n}>()\n</script>\n\n<template>\n <div\n class=\"@container/chat-preview flex flex-col gap-3\"\n :class=\"variant === 'dark' ? 'text-white' : 'text-slate-950'\"\n data-test=\"agent-chat-preview\"\n >\n <div\n v-for=\"(message, index) in messages\"\n :key=\"`${message.sender}-${index}`\"\n class=\"flex\"\n :class=\"message.sender === 'user' ? 'justify-end' : 'justify-start'\"\n data-test=\"agent-chat-preview-message\"\n :data-sender=\"message.sender\"\n >\n <div\n class=\"max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]\"\n :class=\"message.sender === 'user'\n ? 'bg-slate-950 text-white'\n : variant === 'dark'\n ? 'border border-white/10 bg-white/10 text-white'\n : 'border border-slate-200 bg-white text-slate-950'\"\n >\n <ChatRichText\n :text=\"message.text\"\n :inverted=\"message.sender === 'user' || variant === 'dark'\"\n />\n </div>\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;EAcA,IAAM,IAAQ,GACR,IAAS,EAAI,EAAK,GAClB,IAAgB,EAAI,EAAK;EAE/B,SAAS,SAAS;GAChB,EAAO,QAAQ,CAAC,EAAO;EACzB;EAGA,EAAM,GAAQ,OAAO,MAAa;GAChC,AAAI,KAEF,MAAM,EAAS,GACf,iBAAiB;IACf,EAAc,QAAQ;GACxB,GAAG,GAAG,KAGN,EAAc,QAAQ;EAE1B,CAAC;EAED,SAAS,OAAO;GACd,EAAO,QAAQ;EACjB;EAEA,SAAS,QAAQ;GACf,EAAO,QAAQ;EACjB;EAEA,SAAS,cAAc;GACrB,MAAM;EACR;SAEA,EAAa;GACX;GACA;GACA;EACF,CAAC,mBAIC,EAqEY,GAAA;GAnET,KAAK,EAAA;GACL,QAAQ,EAAA;GACR,SAAS,EAAA;GACT,iBAAe,EAAA;;eA+DV,EAnEI,UAAK,KAAO,QAAW,CAQzB,KAAA,EAAA,GADR,EA4DM,OAAA;;IA1DJ,OAAK,EAAA,CAAC,wCAAsC,CAC1B,EAAM,aAAQ,gBAAA,oBAAiD,EAAM,aAAQ,cAAA,kBAA+C,EAAM,aAAQ,aAAA,iBAAA,kBAAA,CAAA,CAAA;OAQ5J,EAgDM,OAAA;IA/CJ,OAAK,EAAA,CAAC,gFAA8E,CAChE,EAAA,QAAA,uHAAA,yGAAA,CAAA,CAAA;IAKnB,OAAK,EAAA,EAAA,oBAA8C,EAAM,aAAQ,gBAAA,gBAAiD,EAAM,aAAQ,cAAA,cAA+C,EAAM,aAAQ,aAAA,aAAA,eAAA,CAAA;IAO7L,SAAK,EAAA,OAAA,EAAA,MAAA,MAAG,EAAA,QAAoB,KAAA,IAAX,OAAM;OAGZ,EAAA,QAeuD,EAAA,IAAA,EAAA,KAfvD,EAAA,GAAZ,EAiBM,OAjBN,GAiBM,CAhBJ,EAUM,OAVN,GAUM,CATJ,EAME,OAAA;IALC,KAAK,EAAM,UAAU;IACrB,KAAG,GAAK,EAAM,YAAY,MAAK;IAChC,OAAM;IACN,OAAM;IACN,QAAO;mCAGT,EAAkL,OAAA,EAA7K,OAAM,qKAAoK,GAAA,MAAA,EAAA,EAAA,CAAA,GAGtK,EAAM,MAAM,MAAM,cAAS,aAAA,EAAA,GAAtC,EAGM,OAHN,GAGM,CAAA,GAAA,EAAA,OAAA,EAAA,KAAA,CAFJ,EAAwH,OAAA;IAAnH,OAAM;IAA2E,OAAA,EAAA,sBAAA,KAAA;iBACtF,EAAkE,OAAA,EAA7D,OAAM,qDAAoD,GAAA,MAAA,EAAA,CAAA,EAAA,CAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,IAM3D,EAAA,SAAU,KAAA,EAAA,GADlB,EAUE,GAAA;;IARA,OAAM;IACL,KAAK;IACE;IACP,SAAS,EAAA;IACT,iBAAe,EAAA;IACf,aAAW;IACX,aAAW,EAAA;IACX,SAAO;;;;;;;;;;;;;;;;;;;;;;;yBE1GhB,EA2BM,OAAA;GA1BJ,OAAK,EAAA,CAAC,+CACE,EAAA,YAAO,SAAA,eAAA,gBAAA,CAAA;GACf,aAAU;cAEV,EAqBM,GAAA,MAAA,EApBuB,EAAA,WAAnB,GAAS,YADnB,EAqBM,OAAA;GAnBH,KAAG,GAAK,EAAQ,OAAM,GAAI;GAC3B,OAAK,EAAA,CAAC,QACE,EAAQ,WAAM,SAAA,gBAAA,eAAA,CAAA;GACtB,aAAU;GACT,eAAa,EAAQ;MAEtB,EAYM,OAAA,EAXJ,OAAK,EAAA,CAAC,oFACE,EAAQ,WAAM,SAAA,4BAA8D,EAAA,YAAO,SAAA,kDAAA,iDAAA,CAAA,EAAA,GAAA,CAM3F,EAGE,GAAA;GAFC,MAAM,EAAQ;GACd,UAAU,EAAQ,WAAM,UAAe,EAAA,YAAO"}
1
+ {"version":3,"file":"agent.js","names":[],"sources":["../agent/ui/AgentWidget.vue","../agent/ui/AgentWidget.vue","../agent/ui/ElAgentChatPreview.vue","../agent/ui/ElAgentChatPreview.vue","../agent/voice/voice-work-state.ts","../agent/voice/voice-session-client.ts"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { PageLinesSDK } from '../../sdkClient'\nimport { nextTick, ref, watch } from 'vue'\nimport AgentChat from './AgentChat.vue'\nimport AgentWrap from './AgentWrap.vue'\n\ninterface Props {\n sdk?: PageLinesSDK\n handle: string\n context?: string\n firstMessage?: string\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n}\n\nconst props = defineProps<Props>()\nconst isOpen = ref(false)\nconst isAgentActive = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\n// Watch isOpen to delay agent activation for smooth transition\nwatch(isOpen, async (newValue) => {\n if (newValue) {\n // When opening, delay agent activation until transition starts\n await nextTick()\n setTimeout(() => {\n isAgentActive.value = true\n }, 400) // Delay to let container expand first\n } else {\n // When closing, immediately deactivate agent\n isAgentActive.value = false\n }\n})\n\nfunction open() {\n isOpen.value = true\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleClose() {\n close()\n}\n\ndefineExpose({\n toggle,\n open,\n close,\n})\n</script>\n\n<template>\n <AgentWrap\n v-slot=\"{ agent, sdk: resolvedSdk }\"\n :sdk=\"sdk\"\n :handle=\"handle\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n >\n <!-- Fixed Widget Wrapper -->\n <div\n v-if=\"agent\"\n class=\"fixed z-[999999] pointer-events-auto\"\n :class=\"[\n props.position === 'bottom-left' ? 'bottom-6 left-6'\n : props.position === 'top-right' ? 'top-6 right-6'\n : props.position === 'top-left' ? 'top-6 left-6'\n : 'bottom-6 right-6',\n ]\"\n >\n <!-- Widget Content with Transition -->\n <div\n class=\"transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu\"\n :class=\"[\n isOpen\n ? 'widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100'\n : 'widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer',\n ]\"\n :style=\"{\n 'transform-origin':\n props.position === 'bottom-left' ? 'bottom left'\n : props.position === 'top-right' ? 'top right'\n : props.position === 'top-left' ? 'top left'\n : 'bottom right',\n }\"\n @click=\"!isOpen ? toggle() : undefined\"\n >\n <!-- Button Content (visible when closed) -->\n <div v-if=\"!isOpen\" class=\"relative h-full w-full\">\n <div class=\"group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100\">\n <img\n :src=\"agent.avatarUrl.value\"\n :alt=\"`${agent.displayName.value} assistant`\"\n class=\"h-full w-full object-cover pointer-events-none\"\n width=\"80\"\n height=\"80\"\n />\n <!-- Gradient overlay for contrast -->\n <div class=\"absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40\" />\n </div>\n <!-- Online indicator -->\n <div v-if=\"agent.state.value.lifecycle === 'running'\" class=\"absolute top-0.5 right-0.5\">\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping\" style=\"animation-duration: 3s;\" />\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white\" />\n </div>\n </div>\n\n <!-- Chat Content (visible when open) -->\n <AgentChat\n v-if=\"isOpen && agent\"\n class=\"rounded-3xl shadow-2xl border border-white/10\"\n :sdk=\"resolvedSdk\"\n :agent=\"agent\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n :has-close=\"true\"\n :is-active=\"isAgentActive\"\n @close=\"handleClose\"\n />\n </div>\n </div>\n </AgentWrap>\n</template>\n","<script setup lang=\"ts\">\nimport type { PageLinesSDK } from '../../sdkClient'\nimport { nextTick, ref, watch } from 'vue'\nimport AgentChat from './AgentChat.vue'\nimport AgentWrap from './AgentWrap.vue'\n\ninterface Props {\n sdk?: PageLinesSDK\n handle: string\n context?: string\n firstMessage?: string\n position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'\n}\n\nconst props = defineProps<Props>()\nconst isOpen = ref(false)\nconst isAgentActive = ref(false)\n\nfunction toggle() {\n isOpen.value = !isOpen.value\n}\n\n// Watch isOpen to delay agent activation for smooth transition\nwatch(isOpen, async (newValue) => {\n if (newValue) {\n // When opening, delay agent activation until transition starts\n await nextTick()\n setTimeout(() => {\n isAgentActive.value = true\n }, 400) // Delay to let container expand first\n } else {\n // When closing, immediately deactivate agent\n isAgentActive.value = false\n }\n})\n\nfunction open() {\n isOpen.value = true\n}\n\nfunction close() {\n isOpen.value = false\n}\n\nfunction handleClose() {\n close()\n}\n\ndefineExpose({\n toggle,\n open,\n close,\n})\n</script>\n\n<template>\n <AgentWrap\n v-slot=\"{ agent, sdk: resolvedSdk }\"\n :sdk=\"sdk\"\n :handle=\"handle\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n >\n <!-- Fixed Widget Wrapper -->\n <div\n v-if=\"agent\"\n class=\"fixed z-[999999] pointer-events-auto\"\n :class=\"[\n props.position === 'bottom-left' ? 'bottom-6 left-6'\n : props.position === 'top-right' ? 'top-6 right-6'\n : props.position === 'top-left' ? 'top-6 left-6'\n : 'bottom-6 right-6',\n ]\"\n >\n <!-- Widget Content with Transition -->\n <div\n class=\"transition-all duration-500 ease-[cubic-bezier(0.25,1,0.33,1)] transform-gpu\"\n :class=\"[\n isOpen\n ? 'widget-expanded w-96 h-[600px] max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)] rounded-3xl opacity-100 scale-100'\n : 'widget-button w-[80px] h-[80px] opacity-100 scale-100 flex items-center justify-center cursor-pointer',\n ]\"\n :style=\"{\n 'transform-origin':\n props.position === 'bottom-left' ? 'bottom left'\n : props.position === 'top-right' ? 'top right'\n : props.position === 'top-left' ? 'top left'\n : 'bottom right',\n }\"\n @click=\"!isOpen ? toggle() : undefined\"\n >\n <!-- Button Content (visible when closed) -->\n <div v-if=\"!isOpen\" class=\"relative h-full w-full\">\n <div class=\"group relative h-full w-full rounded-3xl overflow-hidden shadow-lg transition-all duration-300 ease-[cubic-bezier(0.25,1,0.33,1)] hover:shadow-2xl hover:translate-y-[-1px] hover:brightness-110 active:scale-[1.06] active:-translate-y-0.5 active:duration-100\">\n <img\n :src=\"agent.avatarUrl.value\"\n :alt=\"`${agent.displayName.value} assistant`\"\n class=\"h-full w-full object-cover pointer-events-none\"\n width=\"80\"\n height=\"80\"\n />\n <!-- Gradient overlay for contrast -->\n <div class=\"absolute inset-0 pointer-events-none bg-gradient-to-br from-transparent via-black/10 to-black/40 opacity-60 transition-opacity duration-300 group-hover:opacity-40\" />\n </div>\n <!-- Online indicator -->\n <div v-if=\"agent.state.value.lifecycle === 'running'\" class=\"absolute top-0.5 right-0.5\">\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping\" style=\"animation-duration: 3s;\" />\n <div class=\"size-3 bg-green-500 rounded-full ring-2 ring-white\" />\n </div>\n </div>\n\n <!-- Chat Content (visible when open) -->\n <AgentChat\n v-if=\"isOpen && agent\"\n class=\"rounded-3xl shadow-2xl border border-white/10\"\n :sdk=\"resolvedSdk\"\n :agent=\"agent\"\n :context=\"context\"\n :first-message=\"firstMessage\"\n :has-close=\"true\"\n :is-active=\"isAgentActive\"\n @close=\"handleClose\"\n />\n </div>\n </div>\n </AgentWrap>\n</template>\n","<script setup lang=\"ts\">\nimport ChatRichText from './ChatRichText.vue'\n\nexport type ChatPreviewMessage = {\n sender: 'user' | 'agent'\n text: string\n}\n\ndefineProps<{\n messages: ChatPreviewMessage[]\n variant?: 'light' | 'dark'\n}>()\n</script>\n\n<template>\n <div\n class=\"@container/chat-preview flex flex-col gap-3\"\n :class=\"variant === 'dark' ? 'text-white' : 'text-slate-950'\"\n data-test=\"agent-chat-preview\"\n >\n <div\n v-for=\"(message, index) in messages\"\n :key=\"`${message.sender}-${index}`\"\n class=\"flex\"\n :class=\"message.sender === 'user' ? 'justify-end' : 'justify-start'\"\n data-test=\"agent-chat-preview-message\"\n :data-sender=\"message.sender\"\n >\n <div\n class=\"max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]\"\n :class=\"message.sender === 'user'\n ? 'bg-slate-950 text-white'\n : variant === 'dark'\n ? 'border border-white/10 bg-white/10 text-white'\n : 'border border-slate-200 bg-white text-slate-950'\"\n >\n <ChatRichText\n :text=\"message.text\"\n :inverted=\"message.sender === 'user' || variant === 'dark'\"\n />\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport ChatRichText from './ChatRichText.vue'\n\nexport type ChatPreviewMessage = {\n sender: 'user' | 'agent'\n text: string\n}\n\ndefineProps<{\n messages: ChatPreviewMessage[]\n variant?: 'light' | 'dark'\n}>()\n</script>\n\n<template>\n <div\n class=\"@container/chat-preview flex flex-col gap-3\"\n :class=\"variant === 'dark' ? 'text-white' : 'text-slate-950'\"\n data-test=\"agent-chat-preview\"\n >\n <div\n v-for=\"(message, index) in messages\"\n :key=\"`${message.sender}-${index}`\"\n class=\"flex\"\n :class=\"message.sender === 'user' ? 'justify-end' : 'justify-start'\"\n data-test=\"agent-chat-preview-message\"\n :data-sender=\"message.sender\"\n >\n <div\n class=\"max-w-[88%] rounded-2xl px-4 py-3 text-sm shadow-sm @sm/chat-preview:max-w-[78%]\"\n :class=\"message.sender === 'user'\n ? 'bg-slate-950 text-white'\n : variant === 'dark'\n ? 'border border-white/10 bg-white/10 text-white'\n : 'border border-slate-200 bg-white text-slate-950'\"\n >\n <ChatRichText\n :text=\"message.text\"\n :inverted=\"message.sender === 'user' || variant === 'dark'\"\n />\n </div>\n </div>\n </div>\n</template>\n","import type { ChatEvent } from '@pagelines/core'\n\nexport interface VoiceWorkState {\n turnIds: ReadonlySet<string>\n activeTurnId?: string\n description?: string\n}\n\nexport function createVoiceWorkState(): VoiceWorkState {\n return { turnIds: new Set() }\n}\n\n/** Project canonical conversation events onto the one active voice work line. */\nexport function reduceVoiceWorkEvent(state: VoiceWorkState, event: ChatEvent): VoiceWorkState {\n if (event.event === 'message') {\n const message = event.data.message\n if (message.role === 'user' && message.metadata?.clientContext === 'voice') {\n return { ...state, turnIds: new Set(state.turnIds).add(message.messageId) }\n }\n const turnId = typeof message.metadata?.turnId === 'string' ? message.metadata.turnId : undefined\n if (turnId && state.turnIds.has(turnId)) {\n const turnIds = new Set(state.turnIds)\n turnIds.delete(turnId)\n return state.activeTurnId === turnId ? { turnIds } : { ...state, turnIds }\n }\n return state\n }\n\n const turnId = 'turnId' in event.data && typeof event.data.turnId === 'string'\n ? event.data.turnId\n : undefined\n if (!turnId || !state.turnIds.has(turnId))\n return state\n\n if (event.event === 'working_state') {\n return {\n ...state,\n activeTurnId: turnId,\n description: event.data.description.trim() || 'Working on it',\n }\n }\n if (event.event === 'tool_activity' && event.data.status === 'started') {\n return {\n ...state,\n activeTurnId: turnId,\n description: state.activeTurnId === turnId && state.description\n ? state.description\n : event.data.label,\n }\n }\n if (event.event === 'working_end' || event.event === 'stream_end')\n return { ...state, activeTurnId: undefined, description: undefined }\n return state\n}\n","/** Provider-neutral voice session state machine for web clients. */\nimport type { ApiResponse, VoiceSessionMint } from '@pagelines/core'\nimport type { Ref } from 'vue'\nimport type {\n CreateVoiceProviderClient,\n VoiceLevelEvent,\n VoiceProviderClient,\n VoiceSessionError,\n VoiceSpeakingState,\n} from './provider'\nimport { readChatEventStream, SettingsObject, zVoiceSessionMint } from '@pagelines/core'\nimport { ref } from 'vue'\nimport { resolveApiBase } from '../../api'\nimport { createVoiceProviderClient, isVoiceAudible } from './provider'\nimport { createVoiceWorkState, reduceVoiceWorkEvent } from './voice-work-state'\n\nexport type { VoiceLevelEvent, VoiceSessionError, VoiceSpeakingState } from './provider'\nexport { isVoiceAudible, VOICE_SPECTRUM_BANDS, voiceSpectrum } from './provider'\n\nexport type VoiceSessionClientState = 'idle' | 'minting' | 'connecting' | 'live' | 'ended'\nexport type VoiceChimeKind = 'start' | 'end'\nexport interface VoiceWorkStatus { active: boolean, description?: string }\n\n/** Client-owned latency phases — speech-end-to-first-text is measured on the handler. */\nexport type VoiceMetricName = 'voice.ready' | 'voice.first_playback'\n\nexport interface VoiceMetric {\n name: VoiceMetricName\n durationMs: number\n}\n\nexport interface VoiceClientHandlers {\n onStateChange?: (state: VoiceSessionClientState) => void\n onLevel?: (event: VoiceLevelEvent) => void\n onError?: (error: VoiceSessionError) => void\n onThinkingChange?: (thinking: boolean) => void\n onSpeakingChange?: (state: VoiceSpeakingState) => void\n /** Fire-and-forget latency phases for product observability. */\n onMetric?: (metric: VoiceMetric) => void\n onWorkChange?: (work: VoiceWorkStatus) => void\n}\n\nexport interface VoiceSessionClientSettings extends VoiceClientHandlers, Record<string, unknown> {\n agentId: string\n orgId: string\n conversationId?: string\n /** Last message already loaded by the host; the work stream starts after it. */\n latestSequence?: string\n apiBase?: string\n isDev?: boolean\n token?: string\n fetchFn?: typeof fetch\n /** Provider composition seam; the default dynamically loads the minted provider. */\n createProvider?: CreateVoiceProviderClient\n playChime?: (kind: VoiceChimeKind) => void\n /** Test seam; production uses the quiet looping work asset. */\n setWorkCue?: (active: boolean) => void\n}\n\nconst CHIME_PATHS: Record<VoiceChimeKind, string> = {\n start: '/audio/start-call.mp3',\n end: '/audio/end-call.mp3',\n}\nconst CHIME_VOLUME = 0.7\nconst LEVEL_INTERVAL_MS = 50\nconst CONNECT_TIMEOUT_MS = 15_000\n\n/**\n * The readiness cue is the only signal an eyes-off user gets, so it is served\n * from the API origin rather than the embedding page — a relative path would\n * silently 404 wherever the SDK is not hosted by the app itself.\n */\nexport function createBrowserVoiceChimePlayer(apiBase: string): (kind: VoiceChimeKind) => void {\n const players = Object.fromEntries(Object.entries(CHIME_PATHS).map(([kind, path]) => {\n const audio = new Audio(`${apiBase}${path}`)\n audio.preload = 'auto'\n audio.volume = CHIME_VOLUME\n return [kind, audio]\n })) as Record<VoiceChimeKind, HTMLAudioElement>\n return (kind) => {\n players[kind].currentTime = 0\n // Autoplay policy rejects until the surface has a user gesture; a missing\n // cue must never take the call down with it.\n void players[kind].play().catch(() => {})\n }\n}\n\nfunction createBrowserVoiceWorkCue(apiBase: string): (active: boolean) => void {\n const audio = new Audio(`${apiBase}/audio/thinking.mp3`)\n audio.preload = 'auto'\n audio.loop = true\n audio.volume = 0.16\n return (active) => {\n if (active) {\n void audio.play().catch(() => {})\n return\n }\n audio.pause()\n audio.currentTime = 0\n }\n}\n\nexport class VoiceSessionClient extends SettingsObject<VoiceSessionClientSettings> {\n readonly state: Ref<VoiceSessionClientState> = ref('idle')\n readonly isMuted: Ref<boolean> = ref(false)\n\n private provider?: VoiceProviderClient\n private chime?: (kind: VoiceChimeKind) => void\n private workCue?: (active: boolean) => void\n private generation = 0\n private levelTimer: ReturnType<typeof setInterval> | undefined\n private startAtMs = 0\n private liveAtMs = 0\n private firstPlaybackEmitted = false\n private mutePending = false\n private conversationId?: string\n private workAbort?: AbortController\n private workState = createVoiceWorkState()\n private workCuePlaying = false\n private speaking: VoiceSpeakingState = { user: false, agent: false }\n\n constructor(settings: VoiceSessionClientSettings) {\n super('VoiceSessionClient', settings)\n this.chime = settings.playChime\n this.workCue = settings.setWorkCue\n }\n\n async start(): Promise<void> {\n if (this.state.value !== 'idle')\n return\n const generation = ++this.generation\n this.startAtMs = Date.now()\n this.liveAtMs = 0\n this.firstPlaybackEmitted = false\n this.workState = createVoiceWorkState()\n this.setState('minting')\n try {\n const mint = await this.mintSession(generation)\n if (!mint || generation !== this.generation)\n return\n if (this.conversationId)\n this.startWorkStream(this.conversationId, generation)\n this.setState('connecting')\n await this.connect({ mint, generation })\n } catch (error) {\n if (generation !== this.generation)\n return\n this.fail(this.toSessionError(error, 'VOICE_MINT_FAILED'))\n }\n }\n\n async end(): Promise<void> {\n if (this.state.value === 'idle' || this.state.value === 'ended')\n return\n const shouldChime = this.state.value === 'live'\n this.generation++\n if (shouldChime)\n this.playChime('end')\n this.setState('ended')\n await this.teardown()\n }\n\n private async connect(args: { mint: VoiceSessionMint, generation: number }): Promise<void> {\n const { mint, generation } = args\n const provider = await (this.settings.createProvider ?? createVoiceProviderClient)(mint)\n if (generation !== this.generation) {\n await provider.disconnect().catch(() => {})\n return\n }\n this.provider = provider\n\n const connectPromise = provider.connect({\n onDisconnected: (cause) => {\n if (generation !== this.generation || this.provider !== provider)\n return\n if (this.state.value === 'live' && cause === 'agent') {\n this.generation++\n this.playChime('end')\n this.setState('ended')\n void this.teardown()\n return\n }\n this.fail({\n code: this.state.value === 'live' ? 'VOICE_CONNECTION_LOST' : 'VOICE_CONNECT_FAILED',\n message: this.state.value === 'live'\n ? 'The voice connection dropped.'\n : 'Could not open the voice connection.',\n })\n },\n onError: error => {\n if (generation === this.generation && this.provider === provider)\n this.fail(error)\n },\n onThinkingChange: thinking => {\n if (generation === this.generation && this.provider === provider)\n this.settings.onThinkingChange?.(thinking)\n },\n onSpeakingChange: state => {\n if (generation === this.generation && this.provider === provider)\n this.setSpeaking(state)\n },\n })\n let timeoutId: ReturnType<typeof setTimeout> | undefined\n try {\n await Promise.race([\n connectPromise,\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(Object.assign(new Error('Voice connection timed out.'), { code: 'VOICE_CONNECT_TIMEOUT' })), CONNECT_TIMEOUT_MS)\n }),\n ])\n } catch (error) {\n void connectPromise.then(() => provider.disconnect()).catch(() => {})\n if (generation !== this.generation || this.provider !== provider)\n return\n this.fail(this.toSessionError(error, 'VOICE_CONNECT_FAILED'))\n return\n } finally {\n if (timeoutId)\n clearTimeout(timeoutId)\n }\n if (generation !== this.generation || this.provider !== provider)\n return\n\n this.startLevelTimer(provider, generation)\n this.liveAtMs = Date.now()\n this.setState('live')\n this.updateWorkCue()\n this.emitMetric('voice.ready', this.liveAtMs - this.startAtMs)\n this.playChime('start')\n }\n\n /** True while a live provider can accept typed text as a spoken turn. */\n get canSendUserMessage(): boolean {\n return this.state.value === 'live' && !!this.provider?.sendUserMessage\n }\n\n sendUserMessage(text: string): boolean {\n const body = text.trim()\n if (!body || !this.canSendUserMessage)\n return false\n this.provider!.sendUserMessage!(body)\n return true\n }\n\n /** Returns the applied state, or undefined when mute is unavailable or fails. */\n async toggleMuted(): Promise<boolean | undefined> {\n const provider = this.provider\n if (this.state.value !== 'live' || !provider?.setMuted || this.mutePending)\n return undefined\n const generation = this.generation\n const next = !this.isMuted.value\n this.mutePending = true\n try {\n await provider.setMuted(next)\n if (generation !== this.generation || this.provider !== provider || this.state.value !== 'live')\n return undefined\n this.isMuted.value = next\n // Mute clears the caller's activity and nothing else: working, thinking,\n // and playback carry on.\n if (next && this.speaking.user)\n this.setSpeaking({ ...this.speaking, user: false })\n return next\n }\n catch {\n return undefined\n }\n finally {\n this.mutePending = false\n }\n }\n\n private emitMetric(name: VoiceMetricName, durationMs: number): void {\n this.settings.onMetric?.({ name, durationMs: Math.max(0, Math.round(durationMs)) })\n }\n\n private setState(state: VoiceSessionClientState): void {\n if (this.state.value === state)\n return\n this.state.value = state\n this.settings.onStateChange?.(state)\n }\n\n private fail(error: VoiceSessionError): void {\n const shouldChime = this.state.value === 'live'\n this.generation++\n if (shouldChime)\n this.playChime('end')\n this.settings.onError?.(error)\n this.setState('ended')\n void this.teardown()\n }\n\n private async teardown(): Promise<void> {\n this.workAbort?.abort()\n this.workAbort = undefined\n const hadWork = !!this.workState.activeTurnId\n this.workState = createVoiceWorkState()\n if (hadWork)\n this.settings.onWorkChange?.({ active: false })\n this.setSpeaking({ user: false, agent: false })\n this.settings.onThinkingChange?.(false)\n this.setWorkCue(false)\n if (this.levelTimer !== undefined) {\n clearInterval(this.levelTimer)\n this.levelTimer = undefined\n }\n const provider = this.provider\n this.provider = undefined\n this.isMuted.value = false\n this.settings.onLevel?.({ source: 'playback', level: 0 })\n if (provider)\n await provider.disconnect().catch(() => {})\n }\n\n private startLevelTimer(provider: VoiceProviderClient, generation: number): void {\n if (this.levelTimer !== undefined)\n return\n this.levelTimer = setInterval(() => {\n if (generation !== this.generation || this.provider !== provider)\n return\n // The transport's speaker state picks the meter: the mic analyser hears\n // the speaker too, so \"loudest wins\" flickered green over the assistant.\n const level = provider.sampleLevel(this.speaking.agent ? 'playback' : this.speaking.user ? 'mic' : undefined)\n if (!level)\n return\n this.settings.onLevel?.(level)\n if (\n !this.firstPlaybackEmitted\n && level.source === 'playback'\n && isVoiceAudible(level.level)\n && this.liveAtMs > 0\n ) {\n this.firstPlaybackEmitted = true\n this.emitMetric('voice.first_playback', Date.now() - this.liveAtMs)\n }\n }, LEVEL_INTERVAL_MS)\n }\n\n private authHeaders(): Record<string, string> {\n return this.settings.token ? { Authorization: `Bearer ${this.settings.token}` } : {}\n }\n\n private startWorkStream(conversationId: string, generation: number): void {\n this.workAbort?.abort()\n const abort = new AbortController()\n this.workAbort = abort\n const apiBase = resolveApiBase(this.settings.apiBase, this.settings.isDev)\n const fetchFn = this.settings.fetchFn ?? fetch\n const streamUrl = this.settings.latestSequence\n ? `${apiBase}/api/conversations/${encodeURIComponent(conversationId)}/stream?since=${encodeURIComponent(this.settings.latestSequence)}`\n : `${apiBase}/api/conversations/${encodeURIComponent(conversationId)}/stream`\n void (async () => {\n const response = await fetchFn(streamUrl, {\n headers: { Accept: 'text/event-stream', ...this.authHeaders() },\n credentials: 'include',\n signal: abort.signal,\n })\n if (!response.ok || !response.body)\n return\n for await (const event of readChatEventStream(response.body.getReader())) {\n if (abort.signal.aborted || generation !== this.generation)\n return\n const previous = this.workState\n const next = reduceVoiceWorkEvent(previous, event)\n if (next === previous)\n continue\n this.workState = next\n if (next.activeTurnId !== previous.activeTurnId || next.description !== previous.description) {\n this.settings.onWorkChange?.({\n active: !!next.activeTurnId,\n ...(next.description ? { description: next.description } : {}),\n })\n this.updateWorkCue()\n }\n }\n })().catch(() => {})\n }\n\n private setSpeaking(next: VoiceSpeakingState): void {\n const applied = { user: next.user && !this.isMuted.value, agent: next.agent }\n if (applied.user === this.speaking.user && applied.agent === this.speaking.agent)\n return\n this.speaking = applied\n this.settings.onSpeakingChange?.(applied)\n this.updateWorkCue()\n }\n\n /** Duck on speech, not on energy: room tone kept the loop from ever starting. */\n private updateWorkCue(): void {\n this.setWorkCue(\n this.state.value === 'live'\n && !!this.workState.activeTurnId\n && !this.speaking.user\n && !this.speaking.agent,\n )\n }\n\n private setWorkCue(active: boolean): void {\n if (this.workCuePlaying === active)\n return\n this.workCuePlaying = active\n this.workCue ??= createBrowserVoiceWorkCue(resolveApiBase(this.settings.apiBase, this.settings.isDev))\n this.workCue(active)\n }\n\n private playChime(kind: VoiceChimeKind): void {\n this.chime ??= createBrowserVoiceChimePlayer(resolveApiBase(this.settings.apiBase, this.settings.isDev))\n this.chime(kind)\n }\n\n private async post<T>(args: { path: string, body: unknown }): Promise<T> {\n const apiBase = resolveApiBase(this.settings.apiBase, this.settings.isDev)\n const fetchFn = this.settings.fetchFn ?? fetch\n const res = await fetchFn(`${apiBase}${args.path}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...this.authHeaders() },\n credentials: 'include',\n body: JSON.stringify(args.body),\n })\n const result = await res.json() as ApiResponse<T>\n if (!res.ok || !result.ok) {\n const message = !result.ok ? result.error : `HTTP ${res.status}`\n const code = !result.ok && result.code ? result.code : 'VOICE_MINT_FAILED'\n throw Object.assign(new Error(message || 'Voice request failed.'), { code })\n }\n return result.data\n }\n\n private async mintSession(generation: number): Promise<VoiceSessionMint | null> {\n let conversationId = this.settings.conversationId\n if (!conversationId) {\n conversationId = (await this.post<{ conversationId: string }>({\n path: '/api/conversations',\n body: {\n orgId: this.settings.orgId,\n participants: [{ type: 'agent', id: this.settings.agentId }],\n },\n })).conversationId\n if (generation !== this.generation)\n return null\n }\n\n const data = await this.post<unknown>({ path: '/api/voice/sessions', body: { conversationId } })\n if (generation !== this.generation)\n return null\n const parsed = zVoiceSessionMint.safeParse(data)\n if (!parsed.success)\n throw Object.assign(new Error('Voice session mint returned an unexpected shape.'), { code: 'VOICE_MINT_INVALID' })\n this.conversationId = conversationId\n return parsed.data\n }\n\n private toSessionError(error: unknown, fallbackCode: string): VoiceSessionError {\n return error instanceof Error\n ? {\n code: 'code' in error ? String((error as Error & { code: unknown }).code) : fallbackCode,\n message: error.message,\n }\n : { code: fallbackCode, message: 'Voice session failed to start.' }\n }\n}\n"],"mappings":"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"}