@runtypelabs/persona 3.19.0 → 3.21.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.
@@ -3679,6 +3679,28 @@ var SequenceReorderBuffer = class {
3679
3679
  // src/client.ts
3680
3680
  var DEFAULT_ENDPOINT = "https://api.runtype.com/v1/dispatch";
3681
3681
  var DEFAULT_CLIENT_API_BASE = "https://api.runtype.com";
3682
+ function filenameFromMediaType(mediaType) {
3683
+ var _a, _b;
3684
+ const lower = mediaType.toLowerCase();
3685
+ const knownExtensions = {
3686
+ "application/pdf": "pdf",
3687
+ "application/json": "json",
3688
+ "application/zip": "zip",
3689
+ "text/plain": "txt",
3690
+ "text/csv": "csv",
3691
+ "text/markdown": "md"
3692
+ };
3693
+ const ext = knownExtensions[lower];
3694
+ if (ext) return `attachment.${ext}`;
3695
+ const slash = lower.indexOf("/");
3696
+ if (slash > 0) {
3697
+ const subtype = (_b = (_a = lower.slice(slash + 1).split(";")[0]) == null ? void 0 : _a.trim()) != null ? _b : "";
3698
+ if (subtype && subtype !== "octet-stream" && /^[a-z0-9.+-]+$/i.test(subtype)) {
3699
+ return `attachment.${subtype}`;
3700
+ }
3701
+ }
3702
+ return "attachment";
3703
+ }
3682
3704
  var hasValidContent = (message) => {
3683
3705
  if (message.contentParts && message.contentParts.length > 0) {
3684
3706
  return true;
@@ -5631,6 +5653,89 @@ var AgentWidgetClient = class {
5631
5653
  }
5632
5654
  }
5633
5655
  }
5656
+ } else if (payloadType === "agent_media") {
5657
+ const rawMedia = Array.isArray(payload.media) ? payload.media : [];
5658
+ const mediaContentParts = [];
5659
+ for (const part of rawMedia) {
5660
+ if (!part || typeof part !== "object") continue;
5661
+ const rec = part;
5662
+ const partType = typeof rec.type === "string" ? rec.type : void 0;
5663
+ const rawMediaType = typeof rec.mediaType === "string" ? rec.mediaType.toLowerCase() : "";
5664
+ let src = null;
5665
+ let mediaType = "";
5666
+ if (partType === "media") {
5667
+ const data = typeof rec.data === "string" ? rec.data : void 0;
5668
+ if (!data) continue;
5669
+ mediaType = rawMediaType.length > 0 ? rawMediaType : "application/octet-stream";
5670
+ src = `data:${mediaType};base64,${data}`;
5671
+ } else if (partType === "image-url") {
5672
+ const url = typeof rec.url === "string" ? rec.url : void 0;
5673
+ if (!url) continue;
5674
+ mediaType = rawMediaType;
5675
+ src = url;
5676
+ } else if (partType === "file-url") {
5677
+ const url = typeof rec.url === "string" ? rec.url : void 0;
5678
+ if (!url) continue;
5679
+ mediaType = rawMediaType;
5680
+ src = url;
5681
+ } else {
5682
+ continue;
5683
+ }
5684
+ if (!src) continue;
5685
+ if (partType === "image-url" || mediaType.startsWith("image/")) {
5686
+ mediaContentParts.push({
5687
+ type: "image",
5688
+ image: src,
5689
+ ...mediaType ? { mimeType: mediaType } : {}
5690
+ });
5691
+ } else if (mediaType.startsWith("audio/")) {
5692
+ mediaContentParts.push({
5693
+ type: "audio",
5694
+ audio: src,
5695
+ mimeType: mediaType
5696
+ });
5697
+ } else if (mediaType.startsWith("video/")) {
5698
+ mediaContentParts.push({
5699
+ type: "video",
5700
+ video: src,
5701
+ mimeType: mediaType
5702
+ });
5703
+ } else {
5704
+ const resolvedMediaType = mediaType || "application/octet-stream";
5705
+ mediaContentParts.push({
5706
+ type: "file",
5707
+ data: src,
5708
+ mimeType: resolvedMediaType,
5709
+ filename: filenameFromMediaType(resolvedMediaType)
5710
+ });
5711
+ }
5712
+ }
5713
+ if (mediaContentParts.length > 0) {
5714
+ const seq = nextSequence();
5715
+ const toolCallIdRaw = payload.toolCallId;
5716
+ const mediaIdSuffix = typeof toolCallIdRaw === "string" && toolCallIdRaw.length > 0 ? `${toolCallIdRaw}-${seq}` : String(seq);
5717
+ const mediaMessage = {
5718
+ id: `agent-media-${mediaIdSuffix}`,
5719
+ role: "assistant",
5720
+ content: "",
5721
+ contentParts: mediaContentParts,
5722
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5723
+ streaming: false,
5724
+ sequence: seq,
5725
+ agentMetadata: {
5726
+ executionId: payload.executionId,
5727
+ iteration: payload.iteration
5728
+ }
5729
+ };
5730
+ emitMessage(mediaMessage);
5731
+ const prevAssistant = assistantMessage;
5732
+ if (prevAssistant) {
5733
+ prevAssistant.streaming = false;
5734
+ emitMessage(prevAssistant);
5735
+ }
5736
+ assistantMessage = null;
5737
+ assistantMessageRef.current = null;
5738
+ }
5634
5739
  } else if (payloadType === "agent_iteration_complete") {
5635
5740
  } else if (payloadType === "agent_reflection" || payloadType === "agent_reflect") {
5636
5741
  const reflectionId = `agent-reflection-${payload.executionId}-${payload.iteration}`;
@@ -7394,7 +7499,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
7394
7499
  createdAt,
7395
7500
  sequence,
7396
7501
  streaming = false,
7397
- voiceProcessing
7502
+ voiceProcessing,
7503
+ rawContent
7398
7504
  } = options;
7399
7505
  const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
7400
7506
  const message = {
@@ -7407,7 +7513,8 @@ var AgentWidgetSession = class _AgentWidgetSession {
7407
7513
  // Only include optional fields if provided
7408
7514
  ...llmContent !== void 0 && { llmContent },
7409
7515
  ...contentParts !== void 0 && { contentParts },
7410
- ...voiceProcessing !== void 0 && { voiceProcessing }
7516
+ ...voiceProcessing !== void 0 && { voiceProcessing },
7517
+ ...rawContent !== void 0 && { rawContent }
7411
7518
  };
7412
7519
  this.upsertMessage(message);
7413
7520
  return message;
@@ -7472,7 +7579,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
7472
7579
  id,
7473
7580
  createdAt,
7474
7581
  sequence,
7475
- streaming = false
7582
+ streaming = false,
7583
+ voiceProcessing,
7584
+ rawContent
7476
7585
  } = options;
7477
7586
  const messageId = id != null ? id : role === "user" ? generateUserMessageId() : role === "assistant" ? generateAssistantMessageId() : `system-${Date.now()}-${Math.random().toString(16).slice(2)}`;
7478
7587
  const message = {
@@ -7483,7 +7592,9 @@ var AgentWidgetSession = class _AgentWidgetSession {
7483
7592
  sequence: sequence != null ? sequence : this.nextSequence(),
7484
7593
  streaming,
7485
7594
  ...llmContent !== void 0 && { llmContent },
7486
- ...contentParts !== void 0 && { contentParts }
7595
+ ...contentParts !== void 0 && { contentParts },
7596
+ ...voiceProcessing !== void 0 && { voiceProcessing },
7597
+ ...rawContent !== void 0 && { rawContent }
7487
7598
  };
7488
7599
  results.push(message);
7489
7600
  }
@@ -7491,6 +7602,48 @@ var AgentWidgetSession = class _AgentWidgetSession {
7491
7602
  this.callbacks.onMessagesChanged([...this.messages]);
7492
7603
  return results;
7493
7604
  }
7605
+ /**
7606
+ * Convenience method for injecting a registered component directive as
7607
+ * an assistant message — the same shape Persona produces from a streamed
7608
+ * `{ "text": "...", "component": "...", "props": {...} }` payload.
7609
+ *
7610
+ * Sets `content` to `text`, `rawContent` to the JSON directive (so
7611
+ * `extractComponentDirectiveFromMessage` can find it), and forwards
7612
+ * `llmContent` / `id` / `createdAt` / `sequence`.
7613
+ *
7614
+ * @example
7615
+ * session.injectComponentDirective({
7616
+ * component: "DynamicForm",
7617
+ * props: { title: "Book a demo", fields: [...] },
7618
+ * text: "Share your details to book a demo.",
7619
+ * llmContent: "[Showed booking form]"
7620
+ * });
7621
+ */
7622
+ injectComponentDirective(options) {
7623
+ const {
7624
+ component,
7625
+ props = {},
7626
+ text = "",
7627
+ llmContent,
7628
+ id,
7629
+ createdAt,
7630
+ sequence
7631
+ } = options;
7632
+ const directive = {
7633
+ text,
7634
+ component,
7635
+ props
7636
+ };
7637
+ return this.injectMessage({
7638
+ role: "assistant",
7639
+ content: text,
7640
+ rawContent: JSON.stringify(directive),
7641
+ ...llmContent !== void 0 && { llmContent },
7642
+ ...id !== void 0 && { id },
7643
+ ...createdAt !== void 0 && { createdAt },
7644
+ ...sequence !== void 0 && { sequence }
7645
+ });
7646
+ }
7494
7647
  async sendMessage(rawInput, options) {
7495
7648
  var _a, _b, _c, _d, _e;
7496
7649
  const input = rawInput.trim();
@@ -11077,6 +11230,19 @@ var isSafeImageSrc = (src) => {
11077
11230
  if (!src.includes(":")) return true;
11078
11231
  return false;
11079
11232
  };
11233
+ var isSafeMediaSrc = (src) => {
11234
+ const lower = src.toLowerCase();
11235
+ if (lower.startsWith("javascript:")) return false;
11236
+ if (lower.startsWith("data:text/html")) return false;
11237
+ if (lower.startsWith("data:text/javascript")) return false;
11238
+ if (lower.startsWith("data:text/xml")) return false;
11239
+ if (lower.startsWith("data:application/xhtml")) return false;
11240
+ if (lower.startsWith("data:image/svg+xml")) return false;
11241
+ if (/^(?:https?|blob):/i.test(src)) return true;
11242
+ if (lower.startsWith("data:")) return true;
11243
+ if (!src.includes(":")) return true;
11244
+ return false;
11245
+ };
11080
11246
  var MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX = 320;
11081
11247
  var MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX = 320;
11082
11248
  var getMessageImageParts = (message) => {
@@ -11087,6 +11253,24 @@ var getMessageImageParts = (message) => {
11087
11253
  (part) => part.type === "image" && typeof part.image === "string" && part.image.trim().length > 0
11088
11254
  );
11089
11255
  };
11256
+ var getMessageAudioParts = (message) => {
11257
+ if (!message.contentParts || message.contentParts.length === 0) return [];
11258
+ return message.contentParts.filter(
11259
+ (part) => part.type === "audio" && typeof part.audio === "string" && part.audio.trim().length > 0
11260
+ );
11261
+ };
11262
+ var getMessageVideoParts = (message) => {
11263
+ if (!message.contentParts || message.contentParts.length === 0) return [];
11264
+ return message.contentParts.filter(
11265
+ (part) => part.type === "video" && typeof part.video === "string" && part.video.trim().length > 0
11266
+ );
11267
+ };
11268
+ var getMessageFileParts = (message) => {
11269
+ if (!message.contentParts || message.contentParts.length === 0) return [];
11270
+ return message.contentParts.filter(
11271
+ (part) => part.type === "file" && typeof part.data === "string" && part.data.trim().length > 0
11272
+ );
11273
+ };
11090
11274
  var createMessageImagePreviews = (imageParts, hasVisibleText, onPreviewFailed) => {
11091
11275
  if (imageParts.length === 0) return null;
11092
11276
  try {
@@ -11155,6 +11339,109 @@ var createMessageImagePreviews = (imageParts, hasVisibleText, onPreviewFailed) =
11155
11339
  return null;
11156
11340
  }
11157
11341
  };
11342
+ var createMessageAudioPreviews = (audioParts) => {
11343
+ if (audioParts.length === 0) return null;
11344
+ try {
11345
+ const container = createElement(
11346
+ "div",
11347
+ "persona-flex persona-flex-col persona-gap-2"
11348
+ );
11349
+ container.setAttribute("data-message-attachments", "audio");
11350
+ let visible = 0;
11351
+ audioParts.forEach((part) => {
11352
+ if (!isSafeMediaSrc(part.audio)) return;
11353
+ const audioElement = createElement("audio");
11354
+ audioElement.controls = true;
11355
+ audioElement.preload = "metadata";
11356
+ audioElement.src = part.audio;
11357
+ audioElement.style.display = "block";
11358
+ audioElement.style.width = "100%";
11359
+ audioElement.style.maxWidth = `${MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX}px`;
11360
+ container.appendChild(audioElement);
11361
+ visible += 1;
11362
+ });
11363
+ if (visible === 0) {
11364
+ container.remove();
11365
+ return null;
11366
+ }
11367
+ return container;
11368
+ } catch {
11369
+ return null;
11370
+ }
11371
+ };
11372
+ var createMessageVideoPreviews = (videoParts) => {
11373
+ if (videoParts.length === 0) return null;
11374
+ try {
11375
+ const container = createElement(
11376
+ "div",
11377
+ "persona-flex persona-flex-col persona-gap-2"
11378
+ );
11379
+ container.setAttribute("data-message-attachments", "video");
11380
+ let visible = 0;
11381
+ videoParts.forEach((part) => {
11382
+ if (!isSafeMediaSrc(part.video)) return;
11383
+ const videoElement = createElement("video");
11384
+ videoElement.controls = true;
11385
+ videoElement.preload = "metadata";
11386
+ videoElement.src = part.video;
11387
+ videoElement.style.display = "block";
11388
+ videoElement.style.width = "100%";
11389
+ videoElement.style.maxWidth = `${MESSAGE_IMAGE_PREVIEW_MAX_WIDTH_PX}px`;
11390
+ videoElement.style.maxHeight = `${MESSAGE_IMAGE_PREVIEW_MAX_HEIGHT_PX}px`;
11391
+ videoElement.style.borderRadius = "10px";
11392
+ videoElement.style.backgroundColor = "var(--persona-attachment-image-bg, var(--persona-container, #f3f4f6))";
11393
+ container.appendChild(videoElement);
11394
+ visible += 1;
11395
+ });
11396
+ if (visible === 0) {
11397
+ container.remove();
11398
+ return null;
11399
+ }
11400
+ return container;
11401
+ } catch {
11402
+ return null;
11403
+ }
11404
+ };
11405
+ var createMessageFilePreviews = (fileParts) => {
11406
+ if (fileParts.length === 0) return null;
11407
+ try {
11408
+ const container = createElement(
11409
+ "div",
11410
+ "persona-flex persona-flex-col persona-gap-2"
11411
+ );
11412
+ container.setAttribute("data-message-attachments", "files");
11413
+ let visible = 0;
11414
+ fileParts.forEach((part) => {
11415
+ if (!isSafeMediaSrc(part.data)) return;
11416
+ const link = createElement("a");
11417
+ link.href = part.data;
11418
+ link.download = part.filename;
11419
+ link.target = "_blank";
11420
+ link.rel = "noopener noreferrer";
11421
+ link.textContent = part.filename;
11422
+ link.className = "persona-message-file-attachment";
11423
+ link.style.display = "inline-flex";
11424
+ link.style.alignItems = "center";
11425
+ link.style.gap = "6px";
11426
+ link.style.padding = "6px 10px";
11427
+ link.style.borderRadius = "8px";
11428
+ link.style.fontSize = "0.875rem";
11429
+ link.style.textDecoration = "underline";
11430
+ link.style.backgroundColor = "var(--persona-attachment-file-bg, var(--persona-container, #f3f4f6))";
11431
+ link.style.border = "1px solid var(--persona-attachment-file-border, var(--persona-border, #e5e7eb))";
11432
+ link.style.color = "inherit";
11433
+ container.appendChild(link);
11434
+ visible += 1;
11435
+ });
11436
+ if (visible === 0) {
11437
+ container.remove();
11438
+ return null;
11439
+ }
11440
+ return container;
11441
+ } catch {
11442
+ return null;
11443
+ }
11444
+ };
11158
11445
  var createTypingIndicator = () => {
11159
11446
  const container = document.createElement("div");
11160
11447
  container.className = "persona-flex persona-items-center persona-space-x-1 persona-h-5 persona-mt-2";
@@ -11485,6 +11772,27 @@ var createStandardBubble = (message, transform, layoutConfig, actionsConfig, act
11485
11772
  textContentDiv.style.display = "";
11486
11773
  }
11487
11774
  }
11775
+ const audioParts = getMessageAudioParts(message);
11776
+ if (audioParts.length > 0) {
11777
+ const audioPreviews = createMessageAudioPreviews(audioParts);
11778
+ if (audioPreviews) {
11779
+ bubble.appendChild(audioPreviews);
11780
+ }
11781
+ }
11782
+ const videoParts = getMessageVideoParts(message);
11783
+ if (videoParts.length > 0) {
11784
+ const videoPreviews = createMessageVideoPreviews(videoParts);
11785
+ if (videoPreviews) {
11786
+ bubble.appendChild(videoPreviews);
11787
+ }
11788
+ }
11789
+ const fileParts = getMessageFileParts(message);
11790
+ if (fileParts.length > 0) {
11791
+ const filePreviews = createMessageFilePreviews(fileParts);
11792
+ if (filePreviews) {
11793
+ bubble.appendChild(filePreviews);
11794
+ }
11795
+ }
11488
11796
  bubble.appendChild(contentDiv);
11489
11797
  if (showTimestamp && timestampPosition === "below" && message.createdAt) {
11490
11798
  const timestamp = createTimestamp(message, timestampConfig);
@@ -15329,24 +15637,39 @@ function renderComponentDirective(directive, options) {
15329
15637
  return null;
15330
15638
  }
15331
15639
  }
15640
+ function selectDirectiveSource(message) {
15641
+ if (typeof message.rawContent === "string" && message.rawContent.length > 0) {
15642
+ return message.rawContent;
15643
+ }
15644
+ if (typeof message.content === "string") {
15645
+ const trimmed = message.content.trim();
15646
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
15647
+ return message.content;
15648
+ }
15649
+ }
15650
+ return null;
15651
+ }
15332
15652
  function hasComponentDirective(message) {
15333
- if (!message.rawContent) return false;
15653
+ const source = selectDirectiveSource(message);
15654
+ if (!source) return false;
15334
15655
  try {
15335
- const parsed = JSON.parse(message.rawContent);
15656
+ const parsed = JSON.parse(source);
15336
15657
  return typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string";
15337
15658
  } catch {
15338
15659
  return false;
15339
15660
  }
15340
15661
  }
15341
15662
  function extractComponentDirectiveFromMessage(message) {
15342
- if (!message.rawContent) return null;
15663
+ const source = selectDirectiveSource(message);
15664
+ if (!source) return null;
15343
15665
  try {
15344
- const parsed = JSON.parse(message.rawContent);
15666
+ const parsed = JSON.parse(source);
15345
15667
  if (typeof parsed === "object" && parsed !== null && "component" in parsed && typeof parsed.component === "string") {
15668
+ const directive = parsed;
15346
15669
  return {
15347
- component: parsed.component,
15348
- props: parsed.props && typeof parsed.props === "object" && parsed.props !== null ? parsed.props : {},
15349
- raw: message.rawContent
15670
+ component: directive.component,
15671
+ props: directive.props && typeof directive.props === "object" && directive.props !== null ? directive.props : {},
15672
+ raw: source
15350
15673
  };
15351
15674
  }
15352
15675
  } catch {
@@ -20746,6 +21069,12 @@ var createAgentExperience = (mount, initialConfig, runtimeOptions) => {
20746
21069
  }
20747
21070
  return session.injectMessageBatch(optionsList);
20748
21071
  },
21072
+ injectComponentDirective(options) {
21073
+ if (!open && isPanelToggleable()) {
21074
+ setOpenState(true, "system");
21075
+ }
21076
+ return session.injectComponentDirective(options);
21077
+ },
20749
21078
  /** @deprecated Use injectMessage() instead */
20750
21079
  injectTestMessage(event) {
20751
21080
  if (!open && isPanelToggleable()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.19.0",
3
+ "version": "3.21.0",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",