@runtypelabs/voice 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -6
- package/dist/index.cjs +211 -17
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +211 -17
- package/dist/persona.cjs +272 -21
- package/dist/persona.d.cts +23 -4
- package/dist/persona.d.ts +23 -4
- package/dist/persona.js +272 -21
- package/dist/react.cjs +261 -24
- package/dist/react.d.cts +7 -4
- package/dist/react.d.ts +7 -4
- package/dist/react.js +262 -25
- package/dist/types-DaXyPeiV.d.cts +95 -0
- package/dist/types-DaXyPeiV.d.ts +95 -0
- package/package.json +9 -10
- package/dist/types-9SfA7oHc.d.cts +0 -40
- package/dist/types-9SfA7oHc.d.ts +0 -40
package/dist/persona.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/persona.ts
|
|
21
21
|
var persona_exports = {};
|
|
22
22
|
__export(persona_exports, {
|
|
23
|
+
bindVoiceArtifactsToPersona: () => bindVoiceArtifactsToPersona,
|
|
23
24
|
createPersonaVoiceProvider: () => createPersonaVoiceProvider
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(persona_exports);
|
|
@@ -169,9 +170,58 @@ function initialSnapshot() {
|
|
|
169
170
|
error: null,
|
|
170
171
|
errorDetails: void 0,
|
|
171
172
|
interruptionMode: "none",
|
|
172
|
-
canCancel: false
|
|
173
|
+
canCancel: false,
|
|
174
|
+
artifacts: [],
|
|
175
|
+
session: null
|
|
173
176
|
};
|
|
174
177
|
}
|
|
178
|
+
function readString(value) {
|
|
179
|
+
return typeof value === "string" && value ? value : void 0;
|
|
180
|
+
}
|
|
181
|
+
function readArtifactFile(value) {
|
|
182
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
183
|
+
const file = value;
|
|
184
|
+
const path = readString(file.path);
|
|
185
|
+
const mimeType = readString(file.mimeType);
|
|
186
|
+
if (!path || !mimeType) return void 0;
|
|
187
|
+
const language = readString(file.language);
|
|
188
|
+
return { path, mimeType, ...language ? { language } : {} };
|
|
189
|
+
}
|
|
190
|
+
function applyArtifactFrame(existing, frame, identity) {
|
|
191
|
+
if (frame.type === "artifact_start") {
|
|
192
|
+
const title = readString(frame.title);
|
|
193
|
+
const component = readString(frame.component);
|
|
194
|
+
const file = readArtifactFile(frame.file);
|
|
195
|
+
return {
|
|
196
|
+
...identity,
|
|
197
|
+
artifactType: frame.artifactType === "component" ? "component" : "markdown",
|
|
198
|
+
...title ? { title } : {},
|
|
199
|
+
...file ? { file } : {},
|
|
200
|
+
...component ? { component } : {},
|
|
201
|
+
content: "",
|
|
202
|
+
status: "streaming"
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (!existing) return void 0;
|
|
206
|
+
if (frame.type === "artifact_delta") {
|
|
207
|
+
const delta = typeof frame.delta === "string" ? frame.delta : "";
|
|
208
|
+
return delta ? { ...existing, content: existing.content + delta } : void 0;
|
|
209
|
+
}
|
|
210
|
+
if (frame.type === "artifact_update") {
|
|
211
|
+
const component = readString(frame.component) ?? existing.component;
|
|
212
|
+
const props = frame.props && typeof frame.props === "object" && !Array.isArray(frame.props) ? frame.props : existing.props;
|
|
213
|
+
return {
|
|
214
|
+
...existing,
|
|
215
|
+
...component ? { component } : {},
|
|
216
|
+
...props ? { props } : {}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (frame.type === "artifact_complete") return { ...existing, status: "complete" };
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
function readTurnId(msg) {
|
|
223
|
+
return typeof msg.turnId === "string" && msg.turnId ? msg.turnId : null;
|
|
224
|
+
}
|
|
175
225
|
var VoiceClient = class {
|
|
176
226
|
constructor(options) {
|
|
177
227
|
this.options = options;
|
|
@@ -192,6 +242,16 @@ var VoiceClient = class {
|
|
|
192
242
|
stoppedResponse = false;
|
|
193
243
|
responseEnded = true;
|
|
194
244
|
hasPendingAudio = false;
|
|
245
|
+
/**
|
|
246
|
+
* The assistant entry built from clause captions for the turn being spoken. The
|
|
247
|
+
* authoritative transcript replaces it; a stop commits it, because it captions
|
|
248
|
+
* audio the caller already heard.
|
|
249
|
+
*/
|
|
250
|
+
pendingAssistant = null;
|
|
251
|
+
/** Turn whose clause captions were already committed by a stop, so its authoritative text is redundant. */
|
|
252
|
+
committedAssistantTurnId = null;
|
|
253
|
+
activeTurnId = null;
|
|
254
|
+
suppressedArtifactTurnId = null;
|
|
195
255
|
getSnapshot = () => this.snapshot;
|
|
196
256
|
subscribe = (listener) => {
|
|
197
257
|
this.listeners.add(listener);
|
|
@@ -213,7 +273,7 @@ var VoiceClient = class {
|
|
|
213
273
|
if (this.snapshot.status !== "idle" && this.snapshot.status !== "error") return;
|
|
214
274
|
this.cleanup();
|
|
215
275
|
const generation = this.generation;
|
|
216
|
-
this.update({ ...initialSnapshot(), status: "connecting" });
|
|
276
|
+
this.update({ ...initialSnapshot(), session: this.snapshot.session, status: "connecting" });
|
|
217
277
|
try {
|
|
218
278
|
const token = tokenOverride ?? (typeof this.options.clientToken === "function" ? await this.options.clientToken() : this.options.clientToken);
|
|
219
279
|
if (generation !== this.generation) return;
|
|
@@ -228,6 +288,20 @@ var VoiceClient = class {
|
|
|
228
288
|
url.pathname = `${basePath}/ws/agents/${encodeURIComponent(this.options.agentId)}/voice`;
|
|
229
289
|
url.search = "";
|
|
230
290
|
url.searchParams.set("voiceProtocol", "runtype-browser-v1");
|
|
291
|
+
const capabilities = this.options.artifacts ? "partial_transcript,artifacts" : "partial_transcript";
|
|
292
|
+
url.searchParams.set("clientCapabilities", capabilities);
|
|
293
|
+
const sessionId = this.options.sessionId ?? this.snapshot.session?.sessionId;
|
|
294
|
+
if (sessionId) url.searchParams.set("sessionId", sessionId);
|
|
295
|
+
const visitorToken = typeof this.options.visitorToken === "function" ? await this.options.visitorToken() : this.options.visitorToken;
|
|
296
|
+
if (generation !== this.generation) return;
|
|
297
|
+
if (this.options.visitorToken !== void 0 && !visitorToken) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
"Voice visitor credential unavailable. Initialize the client session first."
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
if (visitorToken && !/^cvt_[A-Za-z0-9_-]+$/.test(visitorToken)) {
|
|
303
|
+
throw new Error("Invalid voice visitor credential.");
|
|
304
|
+
}
|
|
231
305
|
url.hash = "";
|
|
232
306
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
233
307
|
audio: { sampleRate: CAPTURE_SAMPLE_RATE, channelCount: 1, echoCancellation: true }
|
|
@@ -251,7 +325,11 @@ var VoiceClient = class {
|
|
|
251
325
|
return;
|
|
252
326
|
}
|
|
253
327
|
this.player = player;
|
|
254
|
-
const socket = new WebSocket(url.toString(), [
|
|
328
|
+
const socket = new WebSocket(url.toString(), [
|
|
329
|
+
"runtype.bearer",
|
|
330
|
+
token,
|
|
331
|
+
...visitorToken ? [visitorToken] : []
|
|
332
|
+
]);
|
|
255
333
|
socket.binaryType = "arraybuffer";
|
|
256
334
|
this.socket = socket;
|
|
257
335
|
socket.onopen = () => {
|
|
@@ -282,6 +360,11 @@ var VoiceClient = class {
|
|
|
282
360
|
this.cleanup();
|
|
283
361
|
this.update({ status: "idle", interimTranscript: null, audioLevel: 0, isMuted: false });
|
|
284
362
|
};
|
|
363
|
+
/** Forget the remembered conversation so the next startCall opens a new one. No-op during a call. */
|
|
364
|
+
resetSession = () => {
|
|
365
|
+
if (this.snapshot.status !== "idle" && this.snapshot.status !== "error") return;
|
|
366
|
+
this.update({ session: null });
|
|
367
|
+
};
|
|
285
368
|
toggleMute = () => {
|
|
286
369
|
this.update({ isMuted: !this.snapshot.isMuted, audioLevel: 0 });
|
|
287
370
|
};
|
|
@@ -296,9 +379,11 @@ var VoiceClient = class {
|
|
|
296
379
|
this.clearPlayback();
|
|
297
380
|
if (this.snapshot.interruptionMode === "none") {
|
|
298
381
|
this.stoppedResponse = !this.responseEnded;
|
|
382
|
+
this.commitClauseCaptions();
|
|
299
383
|
this.setStatus("listening");
|
|
300
384
|
return;
|
|
301
385
|
}
|
|
386
|
+
this.suppressedArtifactTurnId = this.activeTurnId;
|
|
302
387
|
this.awaitingClear = true;
|
|
303
388
|
this.setStatus("listening");
|
|
304
389
|
this.socket.send(JSON.stringify({ type: "cancel" }));
|
|
@@ -340,6 +425,82 @@ var VoiceClient = class {
|
|
|
340
425
|
this.awaitingClear = false;
|
|
341
426
|
this.stoppedResponse = false;
|
|
342
427
|
this.responseEnded = true;
|
|
428
|
+
this.commitClauseCaptions();
|
|
429
|
+
this.committedAssistantTurnId = null;
|
|
430
|
+
this.activeTurnId = null;
|
|
431
|
+
this.suppressedArtifactTurnId = null;
|
|
432
|
+
const settledArtifacts = this.snapshot.artifacts.filter(
|
|
433
|
+
(artifact) => artifact.status === "complete"
|
|
434
|
+
);
|
|
435
|
+
if (settledArtifacts.length !== this.snapshot.artifacts.length)
|
|
436
|
+
this.update({ artifacts: settledArtifacts });
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Folds an `artifact` message into the snapshot. Frames of a turn the caller
|
|
440
|
+
* cancelled are dropped; a playback-only stop leaves the turn running, so its
|
|
441
|
+
* artifacts keep arriving and are kept.
|
|
442
|
+
*/
|
|
443
|
+
applyArtifactMessage(msg) {
|
|
444
|
+
if (this.awaitingClear) return;
|
|
445
|
+
const frame = msg.event;
|
|
446
|
+
if (!frame || typeof frame !== "object" || Array.isArray(frame)) return;
|
|
447
|
+
const event = frame;
|
|
448
|
+
const id = readString(event.id);
|
|
449
|
+
if (!id) return;
|
|
450
|
+
const turnId = readTurnId(msg);
|
|
451
|
+
if (turnId !== null && turnId === this.suppressedArtifactTurnId) return;
|
|
452
|
+
if (turnId !== null) this.activeTurnId = turnId;
|
|
453
|
+
const artifacts = this.snapshot.artifacts;
|
|
454
|
+
const index = artifacts.findIndex((artifact) => artifact.id === id);
|
|
455
|
+
const executionId = readString(msg.executionId);
|
|
456
|
+
const next = applyArtifactFrame(index === -1 ? void 0 : artifacts[index], event, {
|
|
457
|
+
id,
|
|
458
|
+
turnId,
|
|
459
|
+
...executionId ? { executionId } : {}
|
|
460
|
+
});
|
|
461
|
+
if (!next) return;
|
|
462
|
+
this.update({
|
|
463
|
+
artifacts: index === -1 ? [...artifacts, next] : [...artifacts.slice(0, index), next, ...artifacts.slice(index + 1)]
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/** Settles the spoken clauses in place and ends the turn's claim on them. */
|
|
467
|
+
commitClauseCaptions() {
|
|
468
|
+
this.committedAssistantTurnId = this.pendingAssistant?.turnId ?? null;
|
|
469
|
+
const settled = this.pendingAssistant !== null;
|
|
470
|
+
this.pendingAssistant = null;
|
|
471
|
+
if (!settled) return;
|
|
472
|
+
const transcript = this.snapshot.transcript;
|
|
473
|
+
const last = transcript[transcript.length - 1];
|
|
474
|
+
if (last?.role !== "assistant" || last.partial !== true) return;
|
|
475
|
+
const { partial: _partial, ...committed } = last;
|
|
476
|
+
this.update({ transcript: [...transcript.slice(0, -1), committed] });
|
|
477
|
+
}
|
|
478
|
+
/** Extends the turn's spoken-so-far assistant entry, or opens one for a new turn. */
|
|
479
|
+
appendClauseCaption(text, turnId) {
|
|
480
|
+
const transcript = this.snapshot.transcript;
|
|
481
|
+
const last = transcript[transcript.length - 1];
|
|
482
|
+
if (this.pendingAssistant?.turnId === turnId && last?.role === "assistant") {
|
|
483
|
+
return {
|
|
484
|
+
interimTranscript: null,
|
|
485
|
+
transcript: [...transcript.slice(0, -1), { ...last, content: last.content + text }],
|
|
486
|
+
status: "speaking"
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
this.pendingAssistant = { turnId };
|
|
490
|
+
return {
|
|
491
|
+
interimTranscript: null,
|
|
492
|
+
transcript: [
|
|
493
|
+
...transcript,
|
|
494
|
+
{
|
|
495
|
+
role: "assistant",
|
|
496
|
+
content: text,
|
|
497
|
+
timestamp: Date.now(),
|
|
498
|
+
partial: true,
|
|
499
|
+
...turnId ? { turnId } : {}
|
|
500
|
+
}
|
|
501
|
+
],
|
|
502
|
+
status: "speaking"
|
|
503
|
+
};
|
|
343
504
|
}
|
|
344
505
|
clearPlayback() {
|
|
345
506
|
this.playbackRevision += 1;
|
|
@@ -374,32 +535,61 @@ var VoiceClient = class {
|
|
|
374
535
|
return;
|
|
375
536
|
}
|
|
376
537
|
switch (msg.type) {
|
|
377
|
-
case "session_config":
|
|
538
|
+
case "session_config": {
|
|
378
539
|
if (msg.interruptionMode === "none" || msg.interruptionMode === "cancel" || msg.interruptionMode === "barge-in") {
|
|
379
540
|
this.update({ interruptionMode: msg.interruptionMode });
|
|
380
541
|
}
|
|
542
|
+
const sessionId = readString(msg.sessionId);
|
|
543
|
+
const conversationId = readString(msg.conversationId);
|
|
544
|
+
if (sessionId && conversationId) {
|
|
545
|
+
const session = { sessionId, conversationId };
|
|
546
|
+
this.update({ session });
|
|
547
|
+
this.options.onSession?.(session);
|
|
548
|
+
}
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
case "artifact":
|
|
552
|
+
this.applyArtifactMessage(msg);
|
|
381
553
|
break;
|
|
382
554
|
case "transcript_interim":
|
|
383
555
|
this.update({ interimTranscript: typeof msg.text === "string" ? msg.text || null : null });
|
|
384
556
|
break;
|
|
385
|
-
case "
|
|
557
|
+
case "transcript_partial": {
|
|
558
|
+
if (msg.role !== "assistant" || typeof msg.text !== "string" || !msg.text) break;
|
|
559
|
+
this.activeTurnId = readTurnId(msg) ?? this.activeTurnId;
|
|
560
|
+
if (this.awaitingClear || this.stoppedResponse) break;
|
|
561
|
+
this.responseEnded = false;
|
|
562
|
+
this.update(this.appendClauseCaption(msg.text, readTurnId(msg)));
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
case "transcript_final": {
|
|
386
566
|
if (typeof msg.text !== "string" || msg.role !== "user" && msg.role !== "assistant") break;
|
|
387
|
-
|
|
567
|
+
const role = msg.role;
|
|
568
|
+
const text = msg.text;
|
|
569
|
+
if (role === "assistant" && (this.awaitingClear || this.stoppedResponse)) break;
|
|
570
|
+
const turnId = readTurnId(msg);
|
|
571
|
+
if (turnId !== null) this.activeTurnId = turnId;
|
|
572
|
+
if (role === "assistant" && turnId !== null && turnId === this.committedAssistantTurnId)
|
|
573
|
+
break;
|
|
388
574
|
this.responseEnded = false;
|
|
575
|
+
const transcript = this.snapshot.transcript;
|
|
576
|
+
const last = transcript[transcript.length - 1];
|
|
577
|
+
const supersedes = role === "assistant" && this.pendingAssistant !== null && last?.role === "assistant" && (turnId === null || this.pendingAssistant.turnId === null || this.pendingAssistant.turnId === turnId);
|
|
578
|
+
this.pendingAssistant = null;
|
|
579
|
+
const entry = {
|
|
580
|
+
role,
|
|
581
|
+
content: text,
|
|
582
|
+
// INVARIANT: reuse the caption's timestamp so a keyed list does not remount the bubble.
|
|
583
|
+
timestamp: supersedes && last ? last.timestamp : Date.now(),
|
|
584
|
+
...turnId ? { turnId } : {}
|
|
585
|
+
};
|
|
389
586
|
this.update({
|
|
390
587
|
interimTranscript: null,
|
|
391
|
-
transcript: [
|
|
392
|
-
|
|
393
|
-
{
|
|
394
|
-
role: msg.role,
|
|
395
|
-
content: msg.text,
|
|
396
|
-
timestamp: Date.now(),
|
|
397
|
-
...typeof msg.turnId === "string" && msg.turnId ? { turnId: msg.turnId } : {}
|
|
398
|
-
}
|
|
399
|
-
],
|
|
400
|
-
status: this.awaitingClear ? this.snapshot.status : msg.role === "user" ? "thinking" : "speaking"
|
|
588
|
+
transcript: supersedes ? [...transcript.slice(0, -1), entry] : [...transcript, entry],
|
|
589
|
+
status: this.awaitingClear ? this.snapshot.status : role === "user" ? "thinking" : "speaking"
|
|
401
590
|
});
|
|
402
591
|
break;
|
|
592
|
+
}
|
|
403
593
|
case "audio_end": {
|
|
404
594
|
this.responseEnded = true;
|
|
405
595
|
if (this.stoppedResponse) {
|
|
@@ -416,10 +606,12 @@ var VoiceClient = class {
|
|
|
416
606
|
break;
|
|
417
607
|
}
|
|
418
608
|
case "audio_clear":
|
|
609
|
+
this.suppressedArtifactTurnId = this.activeTurnId ?? this.suppressedArtifactTurnId;
|
|
419
610
|
this.clearPlayback();
|
|
420
611
|
this.awaitingClear = false;
|
|
421
612
|
this.stoppedResponse = false;
|
|
422
613
|
this.responseEnded = true;
|
|
614
|
+
this.commitClauseCaptions();
|
|
423
615
|
this.setStatus("listening");
|
|
424
616
|
break;
|
|
425
617
|
case "metrics": {
|
|
@@ -432,8 +624,11 @@ var VoiceClient = class {
|
|
|
432
624
|
// @snake-case-ok: Existing voice wire contract.
|
|
433
625
|
firstAudioMs: number(msg.first_audio_ms),
|
|
434
626
|
// @snake-case-ok: Existing voice wire contract.
|
|
435
|
-
totalMs: number(msg.total_ms)
|
|
627
|
+
totalMs: number(msg.total_ms),
|
|
628
|
+
// @snake-case-ok: Existing voice wire contract.
|
|
629
|
+
firstSynthesisMs: number(msg.first_synthesis_ms),
|
|
436
630
|
// @snake-case-ok: Existing voice wire contract.
|
|
631
|
+
incremental: msg.incremental === true
|
|
437
632
|
}
|
|
438
633
|
});
|
|
439
634
|
break;
|
|
@@ -495,6 +690,7 @@ function createPersonaVoiceProvider(options) {
|
|
|
495
690
|
const transcriptCallbacks = /* @__PURE__ */ new Set();
|
|
496
691
|
const metricsCallbacks = /* @__PURE__ */ new Set();
|
|
497
692
|
const levelCallbacks = /* @__PURE__ */ new Set();
|
|
693
|
+
const artifactCallbacks = /* @__PURE__ */ new Set();
|
|
498
694
|
let previous = client.getSnapshot();
|
|
499
695
|
let unsubscribe = null;
|
|
500
696
|
const onSnapshot = () => {
|
|
@@ -518,12 +714,18 @@ function createPersonaVoiceProvider(options) {
|
|
|
518
714
|
for (const callback of transcriptCallbacks)
|
|
519
715
|
callback("user", snapshot.interimTranscript, false);
|
|
520
716
|
}
|
|
521
|
-
|
|
717
|
+
snapshot.artifacts.forEach((artifact, index) => {
|
|
718
|
+
if (artifact === before.artifacts[index]) return;
|
|
719
|
+
for (const callback of artifactCallbacks) callback(artifact);
|
|
720
|
+
});
|
|
721
|
+
snapshot.transcript.forEach((entry, index) => {
|
|
722
|
+
if (entry === before.transcript[index]) return;
|
|
723
|
+
const isFinal = entry.partial !== true;
|
|
522
724
|
for (const callback of transcriptCallbacks) {
|
|
523
|
-
if (entry.turnId) callback(entry.role, entry.content,
|
|
524
|
-
else callback(entry.role, entry.content,
|
|
725
|
+
if (entry.turnId) callback(entry.role, entry.content, isFinal, { turnId: entry.turnId });
|
|
726
|
+
else callback(entry.role, entry.content, isFinal);
|
|
525
727
|
}
|
|
526
|
-
}
|
|
728
|
+
});
|
|
527
729
|
};
|
|
528
730
|
const subscribe = () => {
|
|
529
731
|
if (unsubscribe) return;
|
|
@@ -544,6 +746,7 @@ function createPersonaVoiceProvider(options) {
|
|
|
544
746
|
transcriptCallbacks.clear();
|
|
545
747
|
metricsCallbacks.clear();
|
|
546
748
|
levelCallbacks.clear();
|
|
749
|
+
artifactCallbacks.clear();
|
|
547
750
|
},
|
|
548
751
|
startListening: async () => {
|
|
549
752
|
subscribe();
|
|
@@ -570,6 +773,9 @@ function createPersonaVoiceProvider(options) {
|
|
|
570
773
|
onLevel: (callback) => {
|
|
571
774
|
levelCallbacks.add(callback);
|
|
572
775
|
},
|
|
776
|
+
onArtifact: (callback) => {
|
|
777
|
+
artifactCallbacks.add(callback);
|
|
778
|
+
},
|
|
573
779
|
getInterruptionMode: () => client.getSnapshot().interruptionMode,
|
|
574
780
|
isBargeInActive: () => !["idle", "error"].includes(client.getSnapshot().status),
|
|
575
781
|
deactivateBargeIn: async () => {
|
|
@@ -578,3 +784,48 @@ function createPersonaVoiceProvider(options) {
|
|
|
578
784
|
stopPlayback: client.stopPlayback
|
|
579
785
|
};
|
|
580
786
|
}
|
|
787
|
+
function toManualUpsert(artifact) {
|
|
788
|
+
const title = artifact.title ?? artifact.file?.path;
|
|
789
|
+
const transcript = artifact.status === "complete";
|
|
790
|
+
if (artifact.artifactType === "component") {
|
|
791
|
+
if (!artifact.component) return null;
|
|
792
|
+
return {
|
|
793
|
+
id: artifact.id,
|
|
794
|
+
artifactType: "component",
|
|
795
|
+
component: artifact.component,
|
|
796
|
+
...title ? { title } : {},
|
|
797
|
+
...artifact.props ? { props: artifact.props } : {},
|
|
798
|
+
transcript
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
id: artifact.id,
|
|
803
|
+
artifactType: "markdown",
|
|
804
|
+
content: artifact.content,
|
|
805
|
+
...title ? { title } : {},
|
|
806
|
+
...artifact.file ? {
|
|
807
|
+
file: {
|
|
808
|
+
path: artifact.file.path,
|
|
809
|
+
mimeType: artifact.file.mimeType,
|
|
810
|
+
...artifact.file.language ? { language: artifact.file.language } : {}
|
|
811
|
+
}
|
|
812
|
+
} : {},
|
|
813
|
+
transcript
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function bindVoiceArtifactsToPersona(provider, widget, options) {
|
|
817
|
+
let released = false;
|
|
818
|
+
let shown = false;
|
|
819
|
+
provider.onArtifact((artifact) => {
|
|
820
|
+
if (released) return;
|
|
821
|
+
const manual = toManualUpsert(artifact);
|
|
822
|
+
if (!manual) return;
|
|
823
|
+
widget.upsertArtifact(manual);
|
|
824
|
+
if (shown || options?.showOnFirst === false) return;
|
|
825
|
+
shown = true;
|
|
826
|
+
widget.showArtifacts();
|
|
827
|
+
});
|
|
828
|
+
return () => {
|
|
829
|
+
released = true;
|
|
830
|
+
};
|
|
831
|
+
}
|
package/dist/persona.d.cts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import { VoiceProvider } from '@runtypelabs/persona';
|
|
2
|
-
import { V as VoiceClientOptions } from './types-
|
|
1
|
+
import { AgentWidgetInitHandle, VoiceProvider } from '@runtypelabs/persona';
|
|
2
|
+
import { b as VoiceArtifact, V as VoiceClientOptions } from './types-DaXyPeiV.cjs';
|
|
3
3
|
|
|
4
|
+
type ArtifactCallback = (artifact: VoiceArtifact) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Persona 4.22 has no artifact hook on `VoiceProvider`, so the adapter adds its
|
|
7
|
+
* own `onArtifact`. Delivery to the widget is the host's, through the widget
|
|
8
|
+
* handle's `upsertArtifact` (see `bindVoiceArtifactsToPersona`).
|
|
9
|
+
*/
|
|
10
|
+
type RuntypePersonaVoiceProvider = VoiceProvider & {
|
|
11
|
+
onArtifact(callback: ArtifactCallback): void;
|
|
12
|
+
};
|
|
13
|
+
/** The part of Persona's widget handle an artifact binding needs. */
|
|
14
|
+
type PersonaArtifactHost = Pick<AgentWidgetInitHandle, 'upsertArtifact' | 'showArtifacts'>;
|
|
4
15
|
/** Adapt the shared client to Persona 4.22's custom voice provider API. */
|
|
5
|
-
declare function createPersonaVoiceProvider(options: VoiceClientOptions):
|
|
16
|
+
declare function createPersonaVoiceProvider(options: VoiceClientOptions): RuntypePersonaVoiceProvider;
|
|
17
|
+
/**
|
|
18
|
+
* Streams a provider's artifacts into an already-initialized Persona widget. The
|
|
19
|
+
* artifact id is the upsert id, so each frame updates the same record in place,
|
|
20
|
+
* and the pane opens once on the first artifact. Returns a release function.
|
|
21
|
+
*/
|
|
22
|
+
declare function bindVoiceArtifactsToPersona(provider: RuntypePersonaVoiceProvider, widget: PersonaArtifactHost, options?: {
|
|
23
|
+
showOnFirst?: boolean;
|
|
24
|
+
}): () => void;
|
|
6
25
|
|
|
7
|
-
export { createPersonaVoiceProvider };
|
|
26
|
+
export { type PersonaArtifactHost, type RuntypePersonaVoiceProvider, bindVoiceArtifactsToPersona, createPersonaVoiceProvider };
|
package/dist/persona.d.ts
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
import { VoiceProvider } from '@runtypelabs/persona';
|
|
2
|
-
import { V as VoiceClientOptions } from './types-
|
|
1
|
+
import { AgentWidgetInitHandle, VoiceProvider } from '@runtypelabs/persona';
|
|
2
|
+
import { b as VoiceArtifact, V as VoiceClientOptions } from './types-DaXyPeiV.js';
|
|
3
3
|
|
|
4
|
+
type ArtifactCallback = (artifact: VoiceArtifact) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Persona 4.22 has no artifact hook on `VoiceProvider`, so the adapter adds its
|
|
7
|
+
* own `onArtifact`. Delivery to the widget is the host's, through the widget
|
|
8
|
+
* handle's `upsertArtifact` (see `bindVoiceArtifactsToPersona`).
|
|
9
|
+
*/
|
|
10
|
+
type RuntypePersonaVoiceProvider = VoiceProvider & {
|
|
11
|
+
onArtifact(callback: ArtifactCallback): void;
|
|
12
|
+
};
|
|
13
|
+
/** The part of Persona's widget handle an artifact binding needs. */
|
|
14
|
+
type PersonaArtifactHost = Pick<AgentWidgetInitHandle, 'upsertArtifact' | 'showArtifacts'>;
|
|
4
15
|
/** Adapt the shared client to Persona 4.22's custom voice provider API. */
|
|
5
|
-
declare function createPersonaVoiceProvider(options: VoiceClientOptions):
|
|
16
|
+
declare function createPersonaVoiceProvider(options: VoiceClientOptions): RuntypePersonaVoiceProvider;
|
|
17
|
+
/**
|
|
18
|
+
* Streams a provider's artifacts into an already-initialized Persona widget. The
|
|
19
|
+
* artifact id is the upsert id, so each frame updates the same record in place,
|
|
20
|
+
* and the pane opens once on the first artifact. Returns a release function.
|
|
21
|
+
*/
|
|
22
|
+
declare function bindVoiceArtifactsToPersona(provider: RuntypePersonaVoiceProvider, widget: PersonaArtifactHost, options?: {
|
|
23
|
+
showOnFirst?: boolean;
|
|
24
|
+
}): () => void;
|
|
6
25
|
|
|
7
|
-
export { createPersonaVoiceProvider };
|
|
26
|
+
export { type PersonaArtifactHost, type RuntypePersonaVoiceProvider, bindVoiceArtifactsToPersona, createPersonaVoiceProvider };
|