@aparte/core 0.17.0 → 0.17.2

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.
@@ -1318,6 +1318,7 @@ class AparteConfig {
1318
1318
  // Tool Registry
1319
1319
  _tools = /* @__PURE__ */ new Map();
1320
1320
  _toolRenderers = /* @__PURE__ */ new Map();
1321
+ _toolSummaries = /* @__PURE__ */ new Map();
1321
1322
  /** Tagged blocks the stream parser recognises in the prose — see {@link registerStreamBlock}. */
1322
1323
  _streamBlocks = /* @__PURE__ */ new Map();
1323
1324
  _segmentDefaults = /* @__PURE__ */ new Map();
@@ -1415,11 +1416,12 @@ class AparteConfig {
1415
1416
  * Set an incremental (streaming) Markdown renderer provider. Optional —
1416
1417
  * when set, the chat bubble uses it to render the assistant message
1417
1418
  * token-by-token DURING streaming (incremental parse + DOM append, O(n)),
1418
- * instead of re-parsing the whole string on every token. When the turn ends,
1419
- * a registered one-shot `setMarkdownProvider` re-renders the finished message
1420
- * at full fidelity; with no one-shot provider registered, what the streaming
1421
- * renderer drew is kept (sanitised), rather than replaced by the built-in
1422
- * escape-and-line-break fallback.
1419
+ * instead of re-parsing the whole string on every token. With no one-shot
1420
+ * `setMarkdownProvider` registered, it also renders a COMPLETE string
1421
+ * (`renderMarkdown`): a restored reply, a non-streaming provider's answer, a
1422
+ * stored segment — so a reply renders the same however it arrived. A registered
1423
+ * one-shot provider takes over for those, and re-renders the finished message
1424
+ * at full fidelity.
1423
1425
  */
1424
1426
  setStreamingMarkdownProvider(fn) {
1425
1427
  this._streamingMarkdownProvider = fn;
@@ -2033,8 +2035,16 @@ class AparteConfig {
2033
2035
  return val === void 0 ? "" : val;
2034
2036
  }
2035
2037
  /**
2036
- * Render Markdown to HTML
2037
- * Fallback: Escapes HTML and converts newlines to <br>
2038
+ * Render a complete Markdown string to HTML: the one-shot provider when one is
2039
+ * registered, else the incremental provider run over the whole string, else core's
2040
+ * default (escape + `<br>`).
2041
+ *
2042
+ * The middle step is what makes the incremental provider enough on its own. Without
2043
+ * it a reply rendered two ways depending on how it arrived: rich while it streamed,
2044
+ * escaped source once it came back complete — a restored conversation, a
2045
+ * non-streaming provider, a stored segment. Same parser, same sanitizer, so the two
2046
+ * cannot disagree. It needs a `document` to write into; without one (a server render)
2047
+ * the default answers, as before.
2038
2048
  */
2039
2049
  renderMarkdown(raw) {
2040
2050
  if (this._markdownProvider) {
@@ -2043,6 +2053,16 @@ class AparteConfig {
2043
2053
  } catch (error) {
2044
2054
  console.warn("[AparteConfig] Markdown provider failed, using fallback:", error);
2045
2055
  }
2056
+ } else if (this._streamingMarkdownProvider && typeof document !== "undefined") {
2057
+ try {
2058
+ const target = document.createElement("div");
2059
+ const renderer = this._streamingMarkdownProvider(target);
2060
+ renderer.write(raw);
2061
+ renderer.end();
2062
+ return this.sanitizeHtml(target.innerHTML);
2063
+ } catch (error) {
2064
+ console.warn("[AparteConfig] Streaming Markdown provider failed on a complete string, using fallback:", error);
2065
+ }
2046
2066
  }
2047
2067
  return this._defaultMarkdownRenderer(raw);
2048
2068
  }
@@ -2174,6 +2194,37 @@ class AparteConfig {
2174
2194
  getToolRenderer(toolName) {
2175
2195
  return this._toolRenderers.get(toolName);
2176
2196
  }
2197
+ /**
2198
+ * Say what a call targets, in the default row, after the tool's name.
2199
+ *
2200
+ * The default row names the tool and keeps its arguments behind a disclosure, so a
2201
+ * reader saw that a file was read, not which one. Replacing the row with
2202
+ * `registerToolRenderer` for that one line meant re-implementing its six states,
2203
+ * the spinner, the disclosure and the relabel contract. This keeps all of them and
2204
+ * adds the line. A tool with its own renderer draws its own row, so its summary is
2205
+ * never read.
2206
+ *
2207
+ * The text is written as text, never parsed as HTML. It is read when the row
2208
+ * renders, on every update to the call (its arguments may still be arriving), and
2209
+ * on every config change — so registering one after a row is on screen shows up on
2210
+ * that row's next update.
2211
+ *
2212
+ * @example
2213
+ * // read_file skills.json
2214
+ * aparteGlobalConfig.registerToolSummary('read_file', (segment) =>
2215
+ * String(segment.toolCall.input['path'] ?? ''));
2216
+ */
2217
+ registerToolSummary(toolName, summary) {
2218
+ this._toolSummaries.set(toolName, summary);
2219
+ }
2220
+ /** Unregister a per-tool summary. */
2221
+ unregisterToolSummary(toolName) {
2222
+ this._toolSummaries.delete(toolName);
2223
+ }
2224
+ /** Get the summary for a specific tool name. Returns undefined if none registered. */
2225
+ getToolSummary(toolName) {
2226
+ return this._toolSummaries.get(toolName);
2227
+ }
2177
2228
  /**
2178
2229
  * Teach the stream parser a tagged block: `<tag attr="…">…</tag>` in the model's
2179
2230
  * prose becomes the segment `toSegment` builds, streamed delta by delta.
@@ -2382,6 +2433,7 @@ class AparteConfig {
2382
2433
  this._fetchedModels.clear();
2383
2434
  this._tools.clear();
2384
2435
  this._toolRenderers.clear();
2436
+ this._toolSummaries.clear();
2385
2437
  this._streamBlocks.clear();
2386
2438
  this._modelConfig = {};
2387
2439
  this._requireModelSelection = false;
@@ -2467,6 +2519,7 @@ function subscribeConfigChange(el2, handler) {
2467
2519
  window.addEventListener(APARTE_CONFIG_CHANGE, listener);
2468
2520
  return () => window.removeEventListener(APARTE_CONFIG_CHANGE, listener);
2469
2521
  }
2522
+ const settledFromParser = /* @__PURE__ */ new WeakMap();
2470
2523
  const ONE_SHOT_PROBE = "<i>a</i>";
2471
2524
  function hasOneShotMarkdownProvider() {
2472
2525
  return contextConfig().renderMarkdown(ONE_SHOT_PROBE) !== escapeHtml(ONE_SHOT_PROBE);
@@ -2474,6 +2527,7 @@ function hasOneShotMarkdownProvider() {
2474
2527
  function writeStreamedMarkdown(host, contentEl, content, streaming) {
2475
2528
  if (streaming) {
2476
2529
  if (host._aparteSmd === void 0) {
2530
+ settledFromParser.delete(contentEl);
2477
2531
  const renderer = contextConfig().createStreamingMarkdownRenderer(contentEl);
2478
2532
  if (renderer) {
2479
2533
  contentEl.textContent = "";
@@ -2502,9 +2556,13 @@ function writeStreamedMarkdown(host, contentEl, content, streaming) {
2502
2556
  host._aparteSmd = void 0;
2503
2557
  if (!hasOneShotMarkdownProvider()) {
2504
2558
  contentEl.innerHTML = contextConfig().sanitizeHtml(contentEl.innerHTML);
2559
+ settledFromParser.set(contentEl, content);
2505
2560
  return;
2506
2561
  }
2562
+ } else if (settledFromParser.get(contentEl) === content && !hasOneShotMarkdownProvider()) {
2563
+ return;
2507
2564
  }
2565
+ settledFromParser.delete(contentEl);
2508
2566
  contentEl.innerHTML = contextConfig().renderMarkdown(content);
2509
2567
  }
2510
2568
  function highlightMarkdownFences(contentEl) {
@@ -2843,12 +2901,31 @@ function highlightParts(element) {
2843
2901
  });
2844
2902
  }
2845
2903
  }
2904
+ function toolTarget(segment) {
2905
+ const summary = contextConfig().getToolSummary(segment.toolCall?.name ?? "");
2906
+ if (!summary) return "";
2907
+ try {
2908
+ const text = summary(segment);
2909
+ return typeof text === "string" ? text.trim() : "";
2910
+ } catch {
2911
+ return "";
2912
+ }
2913
+ }
2914
+ function setTarget(element, segment) {
2915
+ const el2 = element.querySelector(".aparte-tool-target");
2916
+ if (!el2) return;
2917
+ const text = toolTarget(segment);
2918
+ if (el2.textContent !== text) el2.textContent = text;
2919
+ el2.toggleAttribute("hidden", !text);
2920
+ }
2846
2921
  function pillMarkup(segment, name, status) {
2847
2922
  const badge = stateBadge(segment);
2923
+ const target = toolTarget(segment);
2848
2924
  return `
2849
2925
  <span class="aparte-tool-label">
2850
2926
  <span class="aparte-tool-icon">${contextConfig().getIcon("tool")}</span>
2851
2927
  <span class="aparte-tool-name">${escapeHtml(name)}</span>
2928
+ <span class="aparte-tool-target"${target ? "" : " hidden"}>${escapeHtml(target)}</span>
2852
2929
  </span>
2853
2930
  <span class="aparte-spinner aparte-tool-spinner" aria-hidden="true"${status === "pending" ? "" : " hidden"}></span>
2854
2931
  <span class="aparte-tool-state">${badge}</span>`;
@@ -2913,6 +2990,7 @@ const toolCallRenderer = {
2913
2990
  }
2914
2991
  const icon = element.querySelector(".aparte-tool-icon");
2915
2992
  if (icon) icon.innerHTML = cfg.getIcon("tool");
2993
+ setTarget(element, segment);
2916
2994
  const statusEl = element.querySelector(".aparte-tool-state");
2917
2995
  if (statusEl) statusEl.innerHTML = stateBadge(segment);
2918
2996
  },
@@ -2953,6 +3031,7 @@ const toolCallRenderer = {
2953
3031
  const badge = element.querySelector(".aparte-tool-state");
2954
3032
  if (badge) badge.innerHTML = stateBadge(segment);
2955
3033
  element.querySelector(".aparte-tool-spinner")?.toggleAttribute("hidden", status !== "pending");
3034
+ setTarget(element, segment);
2956
3035
  setPart(element, "input", input);
2957
3036
  setPart(element, "output", segment.result ?? "");
2958
3037
  highlightParts(element);
@@ -6908,7 +6987,8 @@ export {
6908
6987
  isTerminalStatus as ag,
6909
6988
  adoptMessageSegments as ah,
6910
6989
  APARTE_DERIVED_SEGMENTS as ai,
6911
- rootOf as aj,
6990
+ chatBoundaryOf as aj,
6991
+ rootOf as ak,
6912
6992
  APARTE_DEFAULT_BUBBLE_ACTIONS as b,
6913
6993
  APARTE_DEFAULT_HOST_HANDLERS as c,
6914
6994
  APARTE_DEFAULT_ICON_FALLBACKS as d,
@@ -6935,4 +7015,4 @@ export {
6935
7015
  collectRendererStyles as y,
6936
7016
  contentToText as z
6937
7017
  };
6938
- //# sourceMappingURL=dialog-triggers-Bb-3EkT4.js.map
7018
+ //# sourceMappingURL=dialog-triggers-DwTGKWeU.js.map