@pagelines/sdk 1.0.822 → 1.0.823

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.
@@ -6,6 +6,8 @@ export interface VoiceLevelEvent {
6
6
  /** Speech-spectrum energies, low pitch first. */
7
7
  bands?: readonly number[];
8
8
  }
9
+ /** One speech/noise boundary shared by audible UI state and work-cue ducking. */
10
+ export declare function isVoiceAudible(level: number): boolean;
9
11
  export interface VoiceSessionError {
10
12
  code: string;
11
13
  message: string;
@@ -2,7 +2,7 @@ import { Ref } from 'vue';
2
2
  import { CreateVoiceProviderClient, VoiceLevelEvent, VoiceSessionError } from './provider';
3
3
  import { SettingsObject } from '@pagelines/core';
4
4
  export type { VoiceLevelEvent, VoiceSessionError } from './provider';
5
- export { VOICE_SPECTRUM_BANDS, voiceSpectrum } from './provider';
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';
8
8
  export interface VoiceWorkStatus {
@@ -28,6 +28,8 @@ export interface VoiceSessionClientSettings extends VoiceClientHandlers, Record<
28
28
  agentId: string;
29
29
  orgId: string;
30
30
  conversationId?: string;
31
+ /** Last message already loaded by the host; the work stream starts after it. */
32
+ latestSequence?: string;
31
33
  apiBase?: string;
32
34
  isDev?: boolean;
33
35
  token?: string;
@@ -48,8 +50,8 @@ export declare class VoiceSessionClient extends SettingsObject<VoiceSessionClien
48
50
  readonly state: Ref<VoiceSessionClientState>;
49
51
  readonly isMuted: Ref<boolean>;
50
52
  private provider?;
51
- private readonly chime;
52
- private readonly workCue;
53
+ private chime?;
54
+ private workCue?;
53
55
  private generation;
54
56
  private levelTimer;
55
57
  private startAtMs;
@@ -79,6 +81,7 @@ export declare class VoiceSessionClient extends SettingsObject<VoiceSessionClien
79
81
  private startWorkStream;
80
82
  private updateWorkCue;
81
83
  private setWorkCue;
84
+ private playChime;
82
85
  private post;
83
86
  private mintSession;
84
87
  private toSessionError;
package/dist/agent.js CHANGED
@@ -1,18 +1,15 @@
1
1
  import "./rolldown-runtime.js";
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 { t as d } from "./provider.js";
6
- import { SettingsObject as f, readChatEventStream as p, zVoiceSessionMint as m } from "@pagelines/core";
7
- import { Fragment as h, createBlock as g, createCommentVNode as _, createElementBlock as v, createElementVNode as y, createVNode as b, defineComponent as x, nextTick as S, normalizeClass as C, normalizeStyle as w, openBlock as T, ref as E, renderList as D, watch as O, withCtx as k } from "vue";
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";
8
5
  //#region agent/ui/AgentWidget.vue?vue&type=script&setup=true&lang.ts
9
- var A = {
6
+ var T = {
10
7
  key: 0,
11
8
  class: "relative h-full w-full"
12
- }, j = { 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" }, M = ["src", "alt"], N = {
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 = {
13
10
  key: 0,
14
11
  class: "absolute top-0.5 right-0.5"
15
- }, P = /* @__PURE__ */ x({
12
+ }, k = /* @__PURE__ */ g({
16
13
  __name: "AgentWidget",
17
14
  props: {
18
15
  sdk: {},
@@ -22,12 +19,12 @@ var A = {
22
19
  position: {}
23
20
  },
24
21
  setup(e, { expose: t }) {
25
- let n = e, r = E(!1), i = E(!1);
22
+ let n = e, r = x(!1), i = x(!1);
26
23
  function toggle() {
27
24
  r.value = !r.value;
28
25
  }
29
- O(r, async (e) => {
30
- e ? (await S(), setTimeout(() => {
26
+ C(r, async (e) => {
27
+ e ? (await _(), setTimeout(() => {
31
28
  i.value = !0;
32
29
  }, 400)) : i.value = !1;
33
30
  });
@@ -44,29 +41,29 @@ var A = {
44
41
  toggle,
45
42
  open,
46
43
  close
47
- }), (t, a) => (T(), g(c, {
44
+ }), (t, a) => (b(), d(o, {
48
45
  sdk: e.sdk,
49
46
  handle: e.handle,
50
47
  context: e.context,
51
48
  "first-message": e.firstMessage
52
49
  }, {
53
- default: k(({ agent: t, sdk: o }) => [t ? (T(), v("div", {
50
+ default: w(({ agent: t, sdk: o }) => [t ? (b(), p("div", {
54
51
  key: 0,
55
- class: C(["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
- }, [y("div", {
57
- class: C(["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: w({ "transform-origin": n.position === "bottom-left" ? "bottom left" : n.position === "top-right" ? "top right" : n.position === "top-left" ? "top left" : "bottom right" }),
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" }),
59
56
  onClick: a[0] || (a[0] = (e) => r.value ? void 0 : toggle())
60
- }, [r.value ? _("", !0) : (T(), v("div", A, [y("div", j, [y("img", {
57
+ }, [r.value ? f("", !0) : (b(), p("div", T, [m("div", E, [m("img", {
61
58
  src: t.avatarUrl.value,
62
59
  alt: `${t.displayName.value} assistant`,
63
60
  class: "h-full w-full object-cover pointer-events-none",
64
61
  width: "80",
65
62
  height: "80"
66
- }, null, 8, M), a[1] || (a[1] = y("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" ? (T(), v("div", N, [...a[2] || (a[2] = [y("div", {
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", {
67
64
  class: "size-3 bg-green-500 rounded-full ring-2 ring-white absolute animate-ping",
68
65
  style: { "animation-duration": "3s" }
69
- }, null, -1), y("div", { class: "size-3 bg-green-500 rounded-full ring-2 ring-white" }, null, -1)])])) : _("", !0)])), r.value && t ? (T(), g(l, {
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, {
70
67
  key: 1,
71
68
  class: "rounded-3xl shadow-2xl border border-white/10",
72
69
  sdk: o,
@@ -82,7 +79,7 @@ var A = {
82
79
  "context",
83
80
  "first-message",
84
81
  "is-active"
85
- ])) : _("", !0)], 6)], 2)) : _("", !0)]),
82
+ ])) : f("", !0)], 6)], 2)) : f("", !0)]),
86
83
  _: 1
87
84
  }, 8, [
88
85
  "sdk",
@@ -91,286 +88,28 @@ var A = {
91
88
  "first-message"
92
89
  ]));
93
90
  }
94
- }), F = ["data-sender"], I = /* @__PURE__ */ x({
91
+ }), A = ["data-sender"], j = /* @__PURE__ */ g({
95
92
  __name: "ElAgentChatPreview",
96
93
  props: {
97
94
  messages: {},
98
95
  variant: {}
99
96
  },
100
97
  setup(e) {
101
- return (t, r) => (T(), v("div", {
102
- class: C(["@container/chat-preview flex flex-col gap-3", e.variant === "dark" ? "text-white" : "text-slate-950"]),
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"]),
103
100
  "data-test": "agent-chat-preview"
104
- }, [(T(!0), v(h, null, D(e.messages, (t, r) => (T(), v("div", {
101
+ }, [(b(!0), p(u, null, S(e.messages, (t, r) => (b(), p("div", {
105
102
  key: `${t.sender}-${r}`,
106
- class: C(["flex", t.sender === "user" ? "justify-end" : "justify-start"]),
103
+ class: v(["flex", t.sender === "user" ? "justify-end" : "justify-start"]),
107
104
  "data-test": "agent-chat-preview-message",
108
105
  "data-sender": t.sender
109
- }, [y("div", { class: C(["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"]) }, [b(n, {
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, {
110
107
  text: t.text,
111
108
  inverted: t.sender === "user" || e.variant === "dark"
112
- }, null, 8, ["text", "inverted"])], 2)], 10, F))), 128))], 2));
109
+ }, null, 8, ["text", "inverted"])], 2)], 10, A))), 128))], 2));
113
110
  }
114
111
  });
115
112
  //#endregion
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 L = {
155
- start: "/audio/start-call.mp3",
156
- end: "/audio/end-call.mp3"
157
- }, R = .7, z = 50, B = 15e3;
158
- function createBrowserVoiceChimePlayer(e) {
159
- let t = Object.fromEntries(Object.entries(L).map(([t, n]) => {
160
- let r = new Audio(`${e}${n}`);
161
- return r.preload = "auto", r.volume = R, [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 V = .08, H = .08, VoiceSessionClient = class extends f {
178
- constructor(n) {
179
- super("VoiceSessionClient", n), t(this, "state", E("idle")), t(this, "isMuted", E(!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, "audible", !1), this.chime = n.playChime ?? createBrowserVoiceChimePlayer(e(n.apiBase, n.isDev)), this.workCue = n.setWorkCue ?? (n.onWorkChange ? createBrowserVoiceWorkCue(e(n.apiBase, n.isDev)) : () => {});
180
- }
181
- async start() {
182
- if (this.state.value !== "idle") return;
183
- let e = ++this.generation;
184
- this.startAtMs = Date.now(), this.liveAtMs = 0, this.firstPlaybackEmitted = !1, this.workState = createVoiceWorkState(), this.setState("minting");
185
- try {
186
- let t = await this.mintSession(e);
187
- if (!t || e !== this.generation) return;
188
- this.conversationId && this.startWorkStream(this.conversationId, e), this.setState("connecting"), await this.connect({
189
- mint: t,
190
- generation: e
191
- });
192
- } catch (t) {
193
- if (e !== this.generation) return;
194
- this.fail(this.toSessionError(t, "VOICE_MINT_FAILED"));
195
- }
196
- }
197
- async end() {
198
- if (this.state.value === "idle" || this.state.value === "ended") return;
199
- let e = this.state.value === "live";
200
- this.generation++, e && this.chime("end"), this.setState("ended"), await this.teardown();
201
- }
202
- async connect(e) {
203
- let { mint: t, generation: n } = e, r = await (this.settings.createProvider ?? d)(t);
204
- if (n !== this.generation) {
205
- await r.disconnect().catch(() => {});
206
- return;
207
- }
208
- this.provider = r;
209
- let i = r.connect({
210
- onDisconnected: (e) => {
211
- if (!(n !== this.generation || this.provider !== r)) {
212
- if (this.state.value === "live" && e === "agent") {
213
- this.generation++, this.chime("end"), this.setState("ended"), this.teardown();
214
- return;
215
- }
216
- this.fail({
217
- code: this.state.value === "live" ? "VOICE_CONNECTION_LOST" : "VOICE_CONNECT_FAILED",
218
- message: this.state.value === "live" ? "The voice connection dropped." : "Could not open the voice connection."
219
- });
220
- }
221
- },
222
- onError: (e) => {
223
- n === this.generation && this.provider === r && this.fail(e);
224
- },
225
- onThinkingChange: (e) => {
226
- n === this.generation && this.provider === r && this.settings.onThinkingChange?.(e);
227
- }
228
- }), a;
229
- try {
230
- await Promise.race([i, new Promise((e, t) => {
231
- a = setTimeout(() => t(Object.assign(/* @__PURE__ */ Error("Voice connection timed out."), { code: "VOICE_CONNECT_TIMEOUT" })), B);
232
- })]);
233
- } catch (e) {
234
- if (i.then(() => r.disconnect()).catch(() => {}), n !== this.generation || this.provider !== r) return;
235
- this.fail(this.toSessionError(e, "VOICE_CONNECT_FAILED"));
236
- return;
237
- } finally {
238
- a && clearTimeout(a);
239
- }
240
- 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.chime("start"));
241
- }
242
- get canSendUserMessage() {
243
- return this.state.value === "live" && !!this.provider?.sendUserMessage;
244
- }
245
- sendUserMessage(e) {
246
- let t = e.trim();
247
- return !t || !this.canSendUserMessage ? !1 : (this.provider.sendUserMessage(t), !0);
248
- }
249
- async toggleMuted() {
250
- let e = this.provider;
251
- if (this.state.value !== "live" || !e?.setMuted || this.mutePending) return;
252
- let t = this.generation, n = !this.isMuted.value;
253
- this.mutePending = !0;
254
- try {
255
- return await e.setMuted(n), t !== this.generation || this.provider !== e || this.state.value !== "live" ? void 0 : (this.isMuted.value = n, n);
256
- } catch {
257
- return;
258
- } finally {
259
- this.mutePending = !1;
260
- }
261
- }
262
- emitMetric(e, t) {
263
- this.settings.onMetric?.({
264
- name: e,
265
- durationMs: Math.max(0, Math.round(t))
266
- });
267
- }
268
- setState(e) {
269
- this.state.value !== e && (this.state.value = e, this.settings.onStateChange?.(e));
270
- }
271
- fail(e) {
272
- let t = this.state.value === "live";
273
- this.generation++, t && this.chime("end"), this.settings.onError?.(e), this.setState("ended"), this.teardown();
274
- }
275
- async teardown() {
276
- this.workAbort?.abort(), this.workAbort = void 0;
277
- let e = !!this.workState.activeTurnId;
278
- this.workState = createVoiceWorkState(), e && this.settings.onWorkChange?.({ active: !1 }), this.audible = !1, this.settings.onThinkingChange?.(!1), this.setWorkCue(!1), this.levelTimer !== void 0 && (clearInterval(this.levelTimer), this.levelTimer = void 0);
279
- let t = this.provider;
280
- this.provider = void 0, this.isMuted.value = !1, this.settings.onLevel?.({
281
- source: "playback",
282
- level: 0
283
- }), t && await t.disconnect().catch(() => {});
284
- }
285
- startLevelTimer(e, t) {
286
- this.levelTimer === void 0 && (this.levelTimer = setInterval(() => {
287
- if (t !== this.generation || this.provider !== e) return;
288
- let n = e.sampleLevel();
289
- n && (this.settings.onLevel?.(n), this.audible = n.level >= H, this.updateWorkCue(), !this.firstPlaybackEmitted && n.source === "playback" && n.level >= V && this.liveAtMs > 0 && (this.firstPlaybackEmitted = !0, this.emitMetric("voice.first_playback", Date.now() - this.liveAtMs)));
290
- }, z));
291
- }
292
- authHeaders() {
293
- return this.settings.token ? { Authorization: `Bearer ${this.settings.token}` } : {};
294
- }
295
- startWorkStream(t, n) {
296
- if (!this.settings.onWorkChange) return;
297
- this.workAbort?.abort();
298
- let r = new AbortController();
299
- this.workAbort = r;
300
- let i = e(this.settings.apiBase, this.settings.isDev), a = this.settings.fetchFn ?? fetch;
301
- (async () => {
302
- let e = await a(`${i}/api/conversations/${encodeURIComponent(t)}/stream`, {
303
- headers: {
304
- Accept: "text/event-stream",
305
- ...this.authHeaders()
306
- },
307
- credentials: "include",
308
- signal: r.signal
309
- });
310
- if (!(!e.ok || !e.body)) for await (let t of p(e.body.getReader())) {
311
- if (r.signal.aborted || n !== this.generation) return;
312
- let e = this.workState, i = reduceVoiceWorkEvent(e, t);
313
- i !== e && (this.workState = i, (i.activeTurnId !== e.activeTurnId || i.description !== e.description) && (this.settings.onWorkChange?.({
314
- active: !!i.activeTurnId,
315
- ...i.description ? { description: i.description } : {}
316
- }), this.updateWorkCue()));
317
- }
318
- })().catch(() => {});
319
- }
320
- updateWorkCue() {
321
- this.setWorkCue(this.state.value === "live" && !!this.workState.activeTurnId && !this.audible);
322
- }
323
- setWorkCue(e) {
324
- this.workCuePlaying !== e && (this.workCuePlaying = e, this.workCue(e));
325
- }
326
- async post(t) {
327
- let n = e(this.settings.apiBase, this.settings.isDev), r = await (this.settings.fetchFn ?? fetch)(`${n}${t.path}`, {
328
- method: "POST",
329
- headers: {
330
- "Content-Type": "application/json",
331
- ...this.authHeaders()
332
- },
333
- credentials: "include",
334
- body: JSON.stringify(t.body)
335
- }), i = await r.json();
336
- if (!r.ok || !i.ok) {
337
- let e = i.ok ? `HTTP ${r.status}` : i.error, t = !i.ok && i.code ? i.code : "VOICE_MINT_FAILED";
338
- throw Object.assign(Error(e || "Voice request failed."), { code: t });
339
- }
340
- return i.data;
341
- }
342
- async mintSession(e) {
343
- let t = this.settings.conversationId;
344
- if (!t && (t = (await this.post({
345
- path: "/api/conversations",
346
- body: {
347
- orgId: this.settings.orgId,
348
- participants: [{
349
- type: "agent",
350
- id: this.settings.agentId
351
- }]
352
- }
353
- })).conversationId, e !== this.generation)) return null;
354
- let n = await this.post({
355
- path: "/api/voice/sessions",
356
- body: { conversationId: t }
357
- });
358
- if (e !== this.generation) return null;
359
- let r = m.safeParse(n);
360
- if (!r.success) throw Object.assign(/* @__PURE__ */ Error("Voice session mint returned an unexpected shape."), { code: "VOICE_MINT_INVALID" });
361
- return this.conversationId = t, r.data;
362
- }
363
- toSessionError(e, t) {
364
- return e instanceof Error ? {
365
- code: "code" in e ? String(e.code) : t,
366
- message: e.message
367
- } : {
368
- code: t,
369
- message: "Voice session failed to start."
370
- };
371
- }
372
- };
373
- //#endregion
374
- export { i as AGENT_MODES, r as Agent, l as AgentChat, o as AgentChatController, o as AgentController, u as AgentProvider, P as AgentWidget, c as AgentWrap, a as ElAgentChat, I as ElAgentChatPreview, s as VoiceMode, VoiceSessionClient };
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 };
375
114
 
376
115
  //# 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","../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} from './provider'\nimport { readChatEventStream, SettingsObject, zVoiceSessionMint } from '@pagelines/core'\nimport { ref } from 'vue'\nimport { resolveApiBase } from '../../api'\nimport { createVoiceProviderClient } from './provider'\nimport { createVoiceWorkState, reduceVoiceWorkEvent } from './voice-work-state'\n\nexport type { VoiceLevelEvent, VoiceSessionError } from './provider'\nexport { 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 /** 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 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\n/** Playback above this is treated as audible first audio, not room tone. */\nconst FIRST_PLAYBACK_LEVEL = 0.08\n\n/** Anything above this pauses the quiet work loop around live audio. */\nconst AUDIBLE_LEVEL = 0.08\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 readonly chime: (kind: VoiceChimeKind) => void\n private readonly 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 audible = false\n\n constructor(settings: VoiceSessionClientSettings) {\n super('VoiceSessionClient', settings)\n this.chime = settings.playChime\n ?? createBrowserVoiceChimePlayer(resolveApiBase(settings.apiBase, settings.isDev))\n this.workCue = settings.setWorkCue\n ?? (settings.onWorkChange\n ? createBrowserVoiceWorkCue(resolveApiBase(settings.apiBase, settings.isDev))\n : () => {})\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.chime('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.chime('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 })\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.chime('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 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.chime('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.audible = 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 const level = provider.sampleLevel()\n if (!level)\n return\n this.settings.onLevel?.(level)\n this.audible = level.level >= AUDIBLE_LEVEL\n this.updateWorkCue()\n if (\n !this.firstPlaybackEmitted\n && level.source === 'playback'\n && level.level >= FIRST_PLAYBACK_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 if (!this.settings.onWorkChange)\n return\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 void (async () => {\n const response = await fetchFn(`${apiBase}/api/conversations/${encodeURIComponent(conversationId)}/stream`, {\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 updateWorkCue(): void {\n this.setWorkCue(\n this.state.value === 'live'\n && !!this.workState.activeTurnId\n && !this.audible,\n )\n }\n\n private setWorkCue(active: boolean): void {\n if (this.workCuePlaying === active)\n return\n this.workCuePlaying = active\n this.workCue(active)\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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"}
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"}
package/dist/contract.js CHANGED
@@ -4578,6 +4578,7 @@ var ii = object({ conversationId: string().min(1) }).strict(), ai = object({
4578
4578
  ]),
4579
4579
  senderName: string().optional(),
4580
4580
  senderRole: _enum(["owner", "member"]).optional(),
4581
+ sessionContext: string().max(1e3).optional(),
4581
4582
  threadSummary: string(),
4582
4583
  capabilities: array(object({
4583
4584
  name: string().min(1),