@aparte/core 0.25.0 → 0.26.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.
Files changed (69) hide show
  1. package/dist/client/agent-runner.d.ts.map +1 -1
  2. package/dist/client/aparte-client.d.ts +35 -76
  3. package/dist/client/aparte-client.d.ts.map +1 -1
  4. package/dist/client/stream-adapter.d.ts.map +1 -1
  5. package/dist/client/wire-history.d.ts +78 -0
  6. package/dist/client/wire-history.d.ts.map +1 -1
  7. package/dist/components/bubble/aparte-chat-bubble.d.ts +88 -2
  8. package/dist/components/bubble/aparte-chat-bubble.d.ts.map +1 -1
  9. package/dist/components/bubble/bubble-sync.d.ts +5 -1
  10. package/dist/components/bubble/bubble-sync.d.ts.map +1 -1
  11. package/dist/components/bubble/checkpoint-separator.d.ts +40 -0
  12. package/dist/components/bubble/checkpoint-separator.d.ts.map +1 -0
  13. package/dist/components/context/aparte-context.d.ts +157 -25
  14. package/dist/components/context/aparte-context.d.ts.map +1 -1
  15. package/dist/components/viewport/aparte-chat-viewport.d.ts +7 -0
  16. package/dist/components/viewport/aparte-chat-viewport.d.ts.map +1 -1
  17. package/dist/config/aparte-config.d.ts +17 -0
  18. package/dist/config/aparte-config.d.ts.map +1 -1
  19. package/dist/config/locale.d.ts +62 -4
  20. package/dist/config/locale.d.ts.map +1 -1
  21. package/dist/context/checkpoint-state.d.ts +70 -0
  22. package/dist/context/checkpoint-state.d.ts.map +1 -0
  23. package/dist/context/checkpoint.d.ts +28 -0
  24. package/dist/context/checkpoint.d.ts.map +1 -0
  25. package/dist/context/measure.d.ts +148 -0
  26. package/dist/context/measure.d.ts.map +1 -0
  27. package/dist/context/restore.d.ts +24 -0
  28. package/dist/context/restore.d.ts.map +1 -0
  29. package/dist/conversations/conversation-controller.d.ts +13 -4
  30. package/dist/conversations/conversation-controller.d.ts.map +1 -1
  31. package/dist/conversations/conversation-manager.d.ts +6 -0
  32. package/dist/conversations/conversation-manager.d.ts.map +1 -1
  33. package/dist/custom-elements.json +13734 -12139
  34. package/dist/{defer-hydration-BwMqeRHq.js → defer-hydration-8vI8xNNx.js} +683 -250
  35. package/dist/defer-hydration-8vI8xNNx.js.map +1 -0
  36. package/dist/generated/element-attributes.d.ts +8 -2
  37. package/dist/generated/element-attributes.d.ts.map +1 -1
  38. package/dist/host/aparte-chat-host.d.ts +19 -2
  39. package/dist/host/aparte-chat-host.d.ts.map +1 -1
  40. package/dist/index.css +1 -1
  41. package/dist/index.d.ts +5 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +819 -81
  44. package/dist/index.js.map +1 -1
  45. package/dist/index.node.d.ts +4 -1
  46. package/dist/index.node.d.ts.map +1 -1
  47. package/dist/index.node.js +33 -29
  48. package/dist/index.node.js.map +1 -1
  49. package/dist/transport/agent-handler.d.ts.map +1 -1
  50. package/dist/transport/backend-handler.d.ts.map +1 -1
  51. package/dist/transport/types.d.ts +24 -4
  52. package/dist/transport/types.d.ts.map +1 -1
  53. package/dist/types/chat.d.ts +16 -1
  54. package/dist/types/chat.d.ts.map +1 -1
  55. package/dist/types/compactor.d.ts +104 -0
  56. package/dist/types/compactor.d.ts.map +1 -0
  57. package/dist/types/errors.d.ts +2 -0
  58. package/dist/types/errors.d.ts.map +1 -1
  59. package/dist/types/events.d.ts +50 -3
  60. package/dist/types/events.d.ts.map +1 -1
  61. package/dist/types/index.d.ts +3 -2
  62. package/dist/types/index.d.ts.map +1 -1
  63. package/dist/types/model-provider.d.ts +3 -0
  64. package/dist/types/model-provider.d.ts.map +1 -1
  65. package/dist/types/models.d.ts +43 -3
  66. package/dist/types/models.d.ts.map +1 -1
  67. package/dist/types/segments.d.ts +1 -1
  68. package/package.json +2 -2
  69. package/dist/defer-hydration-BwMqeRHq.js.map +0 -1
@@ -58,9 +58,14 @@ class AparteError extends Error {
58
58
  if (error instanceof AparteError) return error;
59
59
  const message = error instanceof Error ? error.message : String(error);
60
60
  const status = error?.status || error?.statusCode || defaultStatus;
61
- const code = defaultCode !== "UNKNOWN_ERROR" ? defaultCode : AparteError.codeForStatus(status) ?? AparteError.codeForFailure(error) ?? defaultCode;
61
+ const carried = error?.code;
62
+ const code = defaultCode !== "UNKNOWN_ERROR" ? defaultCode : AparteError.carriedCode(carried) ?? AparteError.codeForStatus(status) ?? AparteError.codeForFailure(error) ?? defaultCode;
62
63
  return new AparteError(message, code, void 0, error, status);
63
64
  }
65
+ /** The value as an {@link AparteErrorCode}, when it is one — a Node `ECONNRESET` or a vendor's own string is not. */
66
+ static carriedCode(value) {
67
+ return typeof value === "string" && Object.values(AparteErrorCode).includes(value) ? value : void 0;
68
+ }
64
69
  /**
65
70
  * What a thrown, statusless error says about itself. `fetch` rejects a network
66
71
  * failure with a TypeError whose message names the fetch ("Failed to fetch",
@@ -104,7 +109,26 @@ const APARTE_DEFAULT_LOCALE = {
104
109
  scrollToBottom: "Scroll to bottom",
105
110
  suggestionsLabel: "Suggested prompts",
106
111
  contextLabel: "Context window",
112
+ contextSystem: "System prompt",
113
+ contextTools: "Tools",
114
+ contextSummary: "Summary",
115
+ contextMessages: "Messages",
116
+ contextOther: "Other",
117
+ contextFree: "Free",
118
+ contextTotal: "Total",
119
+ contextReclaim: "Compacting now would free ~{n} tokens",
120
+ contextCompactNow: "Compact now",
107
121
  compactionSummaryTitle: "Conversation summary",
122
+ checkpointCount: "{n} messages summarised for the model",
123
+ checkpointCountOne: "1 message summarised for the model",
124
+ checkpointLegacy: "Earlier messages were summarised",
125
+ checkpointGroup: "Context summary",
126
+ checkpointShow: "Show summary",
127
+ checkpointHide: "Hide summary",
128
+ checkpointRestore: "Restore",
129
+ checkpointRestoreConfirm: "Send the original messages to the model again?",
130
+ checkpointPending: "Summarising the context…",
131
+ checkpointFailed: "Couldn't summarise the context",
108
132
  toolRunning: "Running",
109
133
  toolCompleted: "Done",
110
134
  toolRejected: "Rejected",
@@ -143,6 +167,7 @@ const APARTE_DEFAULT_LOCALE = {
143
167
  generating: "Generating…",
144
168
  rebuildingPreview: "Rebuilding preview…",
145
169
  typing: "Typing",
170
+ responseCutOff: "Cut off at the model's length limit.",
146
171
  error: "Error",
147
172
  running: "Running...",
148
173
  run: "Run",
@@ -199,15 +224,19 @@ function readAuth(auth) {
199
224
  if (auth && typeof auth === "object") return { key: auth["apiKey"], endpoint: auth["endpoint"] };
200
225
  return {};
201
226
  }
202
- async function vendorErrorMessage(response, label = "HTTP") {
203
- const body = await response.json().catch(() => ({}));
227
+ function messageOf(body, status, label) {
204
228
  const raw = body?.error;
205
229
  const msg = typeof raw === "object" ? raw?.message : raw;
206
- return typeof msg === "string" && msg ? msg : `${label} ${response.status}`;
230
+ return typeof msg === "string" && msg ? msg : `${label} ${status}`;
231
+ }
232
+ function isAparteErrorCode(value) {
233
+ return typeof value === "string" && Object.values(AparteErrorCode).includes(value);
207
234
  }
208
- async function vendorError(response, label = "HTTP") {
209
- const message = await vendorErrorMessage(response, label);
210
- const code = AparteError.codeForStatus(response.status) ?? AparteErrorCode.UNKNOWN_ERROR;
235
+ async function vendorError(response, label = "HTTP", adapter) {
236
+ const body = await response.json().catch(() => void 0);
237
+ const message = messageOf(body, response.status, label);
238
+ const forwarded = body?.error?.code;
239
+ const code = adapter?.classifyError?.(response.status, body) ?? (isAparteErrorCode(forwarded) ? forwarded : void 0) ?? AparteError.codeForStatus(response.status) ?? AparteErrorCode.UNKNOWN_ERROR;
211
240
  return new AparteError(message, code, void 0, void 0, response.status);
212
241
  }
213
242
  async function parseNonStreamText(provider, response, providerId) {
@@ -250,7 +279,7 @@ class AparteDirectTransport {
250
279
  signal: ctx.signal
251
280
  });
252
281
  if (!response.ok) {
253
- throw await vendorError(response);
282
+ throw await vendorError(response, "HTTP", provider);
254
283
  }
255
284
  if (request.stream === false) {
256
285
  return parseNonStreamText(provider, response, ctx.providerId);
@@ -878,12 +907,18 @@ function createAparteChatHandler(options) {
878
907
  const raw = await vendor.text().catch(() => "");
879
908
  let code;
880
909
  let type;
910
+ let body;
881
911
  try {
882
- const parsed = JSON.parse(raw);
912
+ body = JSON.parse(raw);
913
+ const parsed = body;
883
914
  code = parsed?.error?.code;
884
915
  type = parsed?.error?.type;
885
916
  } catch {
886
917
  }
918
+ try {
919
+ code = adapter.classifyError?.(vendor.status, body) ?? code;
920
+ } catch {
921
+ }
887
922
  return new Response(
888
923
  JSON.stringify({
889
924
  error: {
@@ -897,7 +932,12 @@ function createAparteChatHandler(options) {
897
932
  }
898
933
  if (request.stream === false) {
899
934
  const json = await vendor.json().catch(() => ({}));
900
- const text = typeof adapter.parseText === "function" ? adapter.parseText(json) : "";
935
+ let text;
936
+ try {
937
+ text = typeof adapter.parseText === "function" ? adapter.parseText(json) : "";
938
+ } catch {
939
+ return jsonError$1(502, "The vendor answered with an error.");
940
+ }
901
941
  return new Response(JSON.stringify({ text }), {
902
942
  headers: { "Content-Type": "application/json" }
903
943
  });
@@ -915,7 +955,7 @@ function createAparteChatHandler(options) {
915
955
  controller.close();
916
956
  return;
917
957
  }
918
- controller.enqueue(encoder2.encode(JSON.stringify(value) + "\n"));
958
+ controller.enqueue(encoder2.encode(JSON.stringify(relayable(value)) + "\n"));
919
959
  } catch (err) {
920
960
  controller.error(err);
921
961
  }
@@ -927,6 +967,13 @@ function createAparteChatHandler(options) {
927
967
  return new Response(ndjson, { headers: { "Content-Type": "application/x-ndjson" } });
928
968
  };
929
969
  }
970
+ function relayable(event) {
971
+ const e = event;
972
+ if (e?.type !== "error") return event;
973
+ console.error("[aparte] the vendor streamed an error", event);
974
+ const code = typeof e.code === "string" && Object.values(AparteErrorCode).includes(e.code) ? e.code : void 0;
975
+ return { type: "error", message: "The vendor reported an error.", ...code ? { code } : {} };
976
+ }
930
977
  function jsonError$1(status, message) {
931
978
  return new Response(JSON.stringify({ error: { message } }), {
932
979
  status,
@@ -1248,7 +1295,10 @@ function createStreamAdapter(opts) {
1248
1295
  break;
1249
1296
  }
1250
1297
  case "run-done":
1251
- target.updateMessage?.(messageId, { status: "completed" });
1298
+ target.updateMessage?.(messageId, {
1299
+ status: "completed",
1300
+ ...e.finishReason ? { finishReason: e.finishReason } : {}
1301
+ });
1252
1302
  if (e.usage) {
1253
1303
  try {
1254
1304
  target.setUsage?.(messageId, e.usage);
@@ -1519,10 +1569,11 @@ function createAparteAgentHandler(options) {
1519
1569
  console.error("[aparte] agent run failed", error);
1520
1570
  await pendingWrite?.catch(() => {
1521
1571
  });
1572
+ const named = AparteError.from(error).code;
1522
1573
  write({
1523
1574
  type: "run-error",
1524
1575
  message: "The agent run failed.",
1525
- code: error instanceof AparteError ? error.code : "AGENT_RUN_FAILED"
1576
+ code: named === AparteErrorCode.UNKNOWN_ERROR ? "AGENT_RUN_FAILED" : named
1526
1577
  });
1527
1578
  close();
1528
1579
  });
@@ -2023,6 +2074,8 @@ class AparteConfig {
2023
2074
  _modelPreferenceProvider;
2024
2075
  // Per-call approval policy (a mode: plan / ask / auto). `null` = the tools' own flags.
2025
2076
  _approvalPolicy = null;
2077
+ // What makes the context smaller — `@aparte/plugin-compaction` registers itself here. `null` = nothing does.
2078
+ _compactor = null;
2026
2079
  // Conversation persistence (optional, agnostic)
2027
2080
  _conversationManager;
2028
2081
  /*
@@ -2513,7 +2566,11 @@ class AparteConfig {
2513
2566
  try {
2514
2567
  const auth = await this.getKey(providerId);
2515
2568
  const models = await provider.fetchModels(auth);
2569
+ const held = this._fetchedModels.get(providerId);
2570
+ if (models.length === 0 && held?.length) return held;
2571
+ const windowBefore = this.getCurrentModel()?.contextWindow;
2516
2572
  this._fetchedModels.set(providerId, models);
2573
+ if (this.getCurrentModel()?.contextWindow !== windowBefore) this._notify();
2517
2574
  return models;
2518
2575
  } catch (error) {
2519
2576
  console.warn(`[AparteConfig] Failed to refresh models for ${providerId}`, error);
@@ -2637,6 +2694,26 @@ class AparteConfig {
2637
2694
  getApprovalPolicy() {
2638
2695
  return this._approvalPolicy;
2639
2696
  }
2697
+ /**
2698
+ * Register what makes the context smaller: the gauge's Compact now, a separator's
2699
+ * Retry and the client's pre-flight and overflow retry call it. `null` removes it.
2700
+ *
2701
+ * `@aparte/plugin-compaction`'s `setupCompaction()` registers itself here; core never
2702
+ * compacts on its own, so a page without a compactor has a gauge that measures and a
2703
+ * separator whose Retry falls back to the `aparte-compact` event. A chat reads the one
2704
+ * on ITS config first (`resolveConfig(chat).getCompactor()`), then the client's.
2705
+ *
2706
+ * Notifies like every other registration: a mounted gauge has to hear that "Compact
2707
+ * now" became possible after it was drawn, or went away.
2708
+ */
2709
+ setCompactor(compactor) {
2710
+ this._compactor = compactor;
2711
+ this._notify();
2712
+ }
2713
+ /** The registered compactor, or `null`. */
2714
+ getCompactor() {
2715
+ return this._compactor;
2716
+ }
2640
2717
  /**
2641
2718
  * What the policy says about one call, with the tool's own `needsApproval` as the
2642
2719
  * answer when it has no opinion. The one place the two are combined, so the client's
@@ -3377,6 +3454,7 @@ class AparteConfig {
3377
3454
  this._elicitationFieldRenderer = void 0;
3378
3455
  this._modelPreferenceProvider = void 0;
3379
3456
  this._approvalPolicy = null;
3457
+ this._compactor = null;
3380
3458
  this._bubbleActionsConfig = { ...APARTE_DEFAULT_BUBBLE_ACTIONS };
3381
3459
  this._hostHandlers = { ...APARTE_DEFAULT_HOST_HANDLERS };
3382
3460
  this._notify();
@@ -4520,13 +4598,25 @@ function populateBubbleFromMessage(bubble, message, siblingInfo) {
4520
4598
  if (message.usage !== void 0) {
4521
4599
  bubble.setUsage?.(message.usage);
4522
4600
  }
4601
+ bubble.setFinishReason?.(message.finishReason);
4602
+ bubble.setContextSummary?.(message.contextSummary, { legacy: message.compaction === true });
4523
4603
  }
4524
4604
  const APARTE_CONVERSATION_SCHEMA_VERSION = 2;
4525
4605
  const APARTE_DERIVED_SEGMENTS = Symbol.for("aparte.derivedSegments");
4526
4606
  function applyRetention(messages, tree, maxMessages) {
4527
4607
  if (maxMessages <= 0 || messages.length <= maxMessages) return { messages, tree };
4528
- const trimmedFlat = messages.slice(messages.length - maxMessages);
4529
- const cutoffId = trimmedFlat[0]?.id;
4608
+ const cutIndex = messages.length - maxMessages;
4609
+ const trimmedFlat = messages.slice(cutIndex);
4610
+ const first = trimmedFlat[0];
4611
+ const cutoffId = first?.id;
4612
+ let inherited;
4613
+ for (let i = messages.length - 1; i >= 0; i--) {
4614
+ const own = messages[i].contextSummary;
4615
+ if (!own) continue;
4616
+ if (i < cutIndex) inherited = { ...own, count: own.count + (cutIndex - i) };
4617
+ break;
4618
+ }
4619
+ if (first && inherited) trimmedFlat[0] = { ...first, contextSummary: inherited };
4530
4620
  if (!cutoffId || !tree) return { messages: trimmedFlat, tree };
4531
4621
  const treeIds = new Set(tree.messages.map((m) => m.message.id));
4532
4622
  if (!treeIds.has(cutoffId)) return { messages: trimmedFlat, tree };
@@ -4548,7 +4638,7 @@ function applyRetention(messages, tree, maxMessages) {
4548
4638
  }
4549
4639
  const trimmedTree = {
4550
4640
  headId: tree.headId,
4551
- messages: tree.messages.filter((m) => keep.has(m.message.id)).map((m) => m.message.id === cutoffId ? { message: m.message, parentId: null } : m)
4641
+ messages: tree.messages.filter((m) => keep.has(m.message.id)).map((m) => m.message.id === cutoffId ? { message: inherited ? { ...m.message, contextSummary: inherited } : m.message, parentId: null } : m)
4552
4642
  };
4553
4643
  return { messages: trimmedFlat, tree: trimmedTree };
4554
4644
  }
@@ -4579,6 +4669,7 @@ function treeContentOf(tree) {
4579
4669
  const byId = [...tree.messages].sort((a, b) => a.message.id < b.message.id ? -1 : a.message.id > b.message.id ? 1 : 0);
4580
4670
  return stableStringify(byId.map((entry) => ({ parentId: entry.parentId, message: asContent(entry.message) })));
4581
4671
  }
4672
+ const isTitleSource = (m) => m.role === "user" && !m.compaction;
4582
4673
  class AparteConversationManager {
4583
4674
  _adapter;
4584
4675
  _conversations = [];
@@ -4787,7 +4878,7 @@ class AparteConversationManager {
4787
4878
  async _append(convId, msg) {
4788
4879
  const conv = await this._full(convId);
4789
4880
  if (!conv) return;
4790
- const isFirstUserMsg = msg.role === "user" && conv.messages.every((m) => m.role !== "user");
4881
+ const isFirstUserMsg = msg.role === "user" && !msg.compaction && conv.messages.every((m) => m.role !== "user" || m.compaction);
4791
4882
  const title = isFirstUserMsg ? await this._title(msg) : null;
4792
4883
  const fresh = this._find(convId);
4793
4884
  if (!fresh) return;
@@ -4815,7 +4906,7 @@ class AparteConversationManager {
4815
4906
  console.warn(`[AparteConversationManager] updateMessages('${convId}') before its messages were loaded: ignored.`);
4816
4907
  return;
4817
4908
  }
4818
- const firstUser = messages.find((m) => m.role === "user");
4909
+ const firstUser = messages.find(isTitleSource);
4819
4910
  if (this._retention) {
4820
4911
  ({ messages, tree } = applyRetention(messages, tree, this._retention.maxMessages));
4821
4912
  }
@@ -4836,7 +4927,7 @@ class AparteConversationManager {
4836
4927
  }
4837
4928
  let title;
4838
4929
  if (conv.autoTitle) {
4839
- const before = conv.messages.find((m) => m.role === "user");
4930
+ const before = conv.messages.find(isTitleSource);
4840
4931
  if (firstUser && (firstUser.content ?? "") !== (before?.content ?? "")) {
4841
4932
  title = await this._title(firstUser);
4842
4933
  }
@@ -5209,7 +5300,7 @@ class AparteConversationController {
5209
5300
  this._onCompactDone = (e) => {
5210
5301
  const detail = e.detail;
5211
5302
  if (detail?.targetId && detail.targetId !== this._binding.hostId) return;
5212
- queueMicrotask(() => this._persistOnceSummarised(detail?.summary, 0));
5303
+ queueMicrotask(() => this._persistOnceSummarised(detail ?? {}, 0));
5213
5304
  };
5214
5305
  if (typeof window !== "undefined") {
5215
5306
  window.addEventListener("aparte-conversation-select", this._onSelectConversation);
@@ -5414,20 +5505,40 @@ class AparteConversationController {
5414
5505
  * that was partially written before a switch or component destruction.
5415
5506
  */
5416
5507
  /**
5417
- * Save once the binding holds the summary a compaction announced. A transcript kept in
5418
- * a framework store reaches the binding only when that framework renders, so a save on
5419
- * the microtask wrote the conversation as it stood before the compaction. Bounded: a
5420
- * run with no summary (skipped), or a binding that never shows it, is saved as it is.
5421
- */
5422
- _persistOnceSummarised(summary, waits) {
5423
- const holds = !summary || this._binding.getMessages().some(
5424
- (m) => m.compaction === true && typeof m.content === "string" && m.content.endsWith(summary)
5425
- );
5508
+ * Save once the binding holds what a compaction (or a Restore) announced. A transcript
5509
+ * kept in a framework store reaches the binding only when that framework renders, so a
5510
+ * save on the microtask wrote the conversation as it stood before the change. Bounded:
5511
+ * a run that changed nothing (skipped), or a binding that never shows the change, is
5512
+ * saved as it is.
5513
+ *
5514
+ * What "holds" means follows what the run said it did:
5515
+ * - a checkpoint — `messageId` names the message carrying it, and it holds once that
5516
+ * message carries a `contextSummary` with its `createdAt` (a summary without the
5517
+ * stamp is a half-written one);
5518
+ * - a Restore — it holds once no message of the path carries a checkpoint any more;
5519
+ * - an older host's summary MESSAGE (`compaction: true`, ending with the summary).
5520
+ * A run that names none of these has nothing to wait for.
5521
+ */
5522
+ _persistOnceSummarised(detail, waits) {
5523
+ const messages = this._binding.getMessages();
5524
+ let holds = true;
5525
+ if (detail.restored) {
5526
+ holds = !messages.some((m) => m.contextSummary !== void 0);
5527
+ } else if (detail.messageId || detail.summary) {
5528
+ const { messageId, summary } = detail;
5529
+ const checkpointed = !!messageId && messages.some(
5530
+ (m) => m.id === messageId && typeof m.contextSummary?.createdAt === "number"
5531
+ );
5532
+ const summaryMessage = !!summary && messages.some(
5533
+ (m) => m.compaction === true && typeof m.content === "string" && m.content.endsWith(summary)
5534
+ );
5535
+ holds = checkpointed || summaryMessage;
5536
+ }
5426
5537
  if (holds || !this._binding.afterRender || waits >= MAX_COMPACTION_SAVE_WAITS) {
5427
5538
  this._persistActive();
5428
5539
  return;
5429
5540
  }
5430
- this._binding.afterRender(() => this._persistOnceSummarised(summary, waits + 1));
5541
+ this._binding.afterRender(() => this._persistOnceSummarised(detail, waits + 1));
5431
5542
  }
5432
5543
  _persistActive() {
5433
5544
  if (!this._activeId) return;
@@ -5505,16 +5616,126 @@ function gateGlide(el2) {
5505
5616
  el2.addEventListener("aparte-send", lift);
5506
5617
  return off;
5507
5618
  }
5619
+ function chatIdOf(el2) {
5620
+ let node = el2.parentElement;
5621
+ while (node) {
5622
+ const isHost = node.tagName.toLowerCase() === "aparte-chat" || node.hasAttribute("data-aparte-chat");
5623
+ if (isHost && node.id) return node.id;
5624
+ node = node.parentElement;
5625
+ }
5626
+ return void 0;
5627
+ }
5628
+ class CheckpointStateWatcher {
5629
+ constructor(_options) {
5630
+ this._options = _options;
5631
+ }
5632
+ _messageId = null;
5633
+ _state = null;
5634
+ _reason = "";
5635
+ /** The bubble the attributes were last written to, so they can be taken off it even after it left the lookup. */
5636
+ _marked = null;
5637
+ /** Listen on `window` for the three events; returns the function that stops. */
5638
+ start() {
5639
+ const mine = (targetId) => {
5640
+ if (this._options.active && !this._options.active()) return false;
5641
+ return !targetId || targetId === this._options.ownId();
5642
+ };
5643
+ const mineMessage = (messageId) => !!messageId && this._options.owns(messageId);
5644
+ const onStart = (e) => {
5645
+ const detail = e.detail;
5646
+ if (!mine(detail?.targetId) || !mineMessage(detail?.messageId)) return;
5647
+ this.clear();
5648
+ this._set(detail.messageId, "pending", "");
5649
+ };
5650
+ const onDone = (e) => {
5651
+ const detail = e.detail;
5652
+ if (!mine(detail?.targetId) || detail?.skipped) return;
5653
+ if (detail?.messageId && detail.messageId === this._messageId) this.clear();
5654
+ };
5655
+ const onError = (e) => {
5656
+ const detail = e.detail;
5657
+ if (!mine(detail?.targetId) || !mineMessage(detail?.messageId)) return;
5658
+ if (detail.aborted) {
5659
+ if (detail.messageId === this._messageId) this.clear();
5660
+ return;
5661
+ }
5662
+ if (detail.messageId !== this._messageId) this.clear();
5663
+ this._set(detail.messageId, "failed", detail.error ?? "");
5664
+ };
5665
+ window.addEventListener("aparte-compact-start", onStart);
5666
+ window.addEventListener("aparte-compact-done", onDone);
5667
+ window.addEventListener("aparte-compact-error", onError);
5668
+ return () => {
5669
+ window.removeEventListener("aparte-compact-start", onStart);
5670
+ window.removeEventListener("aparte-compact-done", onDone);
5671
+ window.removeEventListener("aparte-compact-error", onError);
5672
+ };
5673
+ }
5674
+ /** Take the state off the bubble and forget it. */
5675
+ clear() {
5676
+ const bubble = this._marked ?? (this._messageId ? this._options.bubbleOf(this._messageId) : null);
5677
+ this._messageId = null;
5678
+ this._state = null;
5679
+ this._reason = "";
5680
+ this._marked = null;
5681
+ bubble?.removeAttribute("data-checkpoint");
5682
+ bubble?.removeAttribute("data-checkpoint-reason");
5683
+ }
5684
+ /** Write the remembered state onto the bubble, which may have been created since. */
5685
+ reapply() {
5686
+ if (!this._messageId || !this._state) return;
5687
+ this._write(this._messageId, this._state, this._reason);
5688
+ }
5689
+ _set(messageId, state, reason) {
5690
+ this._messageId = messageId;
5691
+ this._state = state;
5692
+ this._reason = reason;
5693
+ this._write(messageId, state, reason);
5694
+ }
5695
+ _write(messageId, state, reason) {
5696
+ const bubble = this._options.bubbleOf(messageId);
5697
+ if (!bubble) return;
5698
+ if (this._marked && this._marked !== bubble) {
5699
+ this._marked.removeAttribute("data-checkpoint");
5700
+ this._marked.removeAttribute("data-checkpoint-reason");
5701
+ }
5702
+ this._marked = bubble;
5703
+ if (reason) bubble.setAttribute("data-checkpoint-reason", reason);
5704
+ else bubble.removeAttribute("data-checkpoint-reason");
5705
+ if (bubble.getAttribute("data-checkpoint") !== state) bubble.setAttribute("data-checkpoint", state);
5706
+ }
5707
+ }
5508
5708
  const MAX_SIBLING_RETRIES = 6;
5509
5709
  const TREE_OWNED_FIELDS = /* @__PURE__ */ new Set(["id", "branches", "activeBranchIndex", "isStreaming"]);
5510
5710
  const ALWAYS_PRESENT_FIELDS = /* @__PURE__ */ new Set(["content", "segments", "status"]);
5711
+ const RENDERED_FIELDS = ["content", "segments", "attachments", "usage", "finishReason", "contextSummary", "compaction"];
5511
5712
  class AparteChatHost {
5512
5713
  binding;
5513
5714
  options;
5514
5715
  /** Id of the assistant message currently streaming (null when idle). */
5515
5716
  _streamingId = null;
5516
- /** Message ids already reconciled onto a bubble (skip-rerender cache). */
5517
- _renderedIds = /* @__PURE__ */ new Set();
5717
+ /**
5718
+ * The message object each bubble was last drawn from, by id (skip-rerender cache).
5719
+ *
5720
+ * An id alone could not say whether the list had changed under a bubble: a framework
5721
+ * that owns the transcript (a controlled `messages` prop, a v-model, a store behind a
5722
+ * compaction target) never goes through {@link updateMessage}, it hands over a list
5723
+ * with a NEW object for the message it changed, and a checkpoint written that way to a
5724
+ * message that was not the last never reached its bubble. Immutable updates always
5725
+ * produce a new object, so the reference is the signal; the same object again means
5726
+ * nothing changed and the bubble is left alone.
5727
+ */
5728
+ _renderedIds = /* @__PURE__ */ new Map();
5729
+ /**
5730
+ * Says on the bubble what a compaction is doing to the message it will sit on. The
5731
+ * viewport answers for this in a chat with no framework; here the framework owns the
5732
+ * list and the host is the one that knows which bubble is whose.
5733
+ */
5734
+ _checkpointState = new CheckpointStateWatcher({
5735
+ ownId: () => this.binding.hostId,
5736
+ bubbleOf: (id) => this._bubbleById(id),
5737
+ owns: (id) => this.binding.getMessages().some((m) => m.id === id)
5738
+ });
5518
5739
  /** Sibling info from the last path-change, applied after the next render. */
5519
5740
  _pendingSiblings;
5520
5741
  /** How many render passes the sibling write has waited for — see `_applyPendingSiblings`. */
@@ -5600,6 +5821,7 @@ class AparteChatHost {
5600
5821
  }
5601
5822
  this._installHostMethods(host2);
5602
5823
  this._installLifecycleListeners(host2);
5824
+ this._teardown.push(this._checkpointState.start());
5603
5825
  this._teardown.push(gateGlide(host2));
5604
5826
  this._installViewportListeners();
5605
5827
  this._createController(host2);
@@ -5713,6 +5935,7 @@ class AparteChatHost {
5713
5935
  this._setStreamingId(null);
5714
5936
  this.binding.onTypingChange?.(false);
5715
5937
  this._renderedIds.clear();
5938
+ this._checkpointState.clear();
5716
5939
  this.binding.resetComposer?.();
5717
5940
  }
5718
5941
  // ── host method surface (also installed on the host element) ───────────
@@ -5762,6 +5985,13 @@ class AparteChatHost {
5762
5985
  if (updates.status) {
5763
5986
  this._bubbleById(messageId)?.updateMessage?.({ status: updates.status });
5764
5987
  }
5988
+ if (index !== msgs.length - 1 && RENDERED_FIELDS.some((key) => key in updates)) {
5989
+ this._renderedIds.delete(messageId);
5990
+ const bubble = this._bubbleById(messageId);
5991
+ if (bubble && ("contextSummary" in updates || "compaction" in updates)) {
5992
+ bubble.setContextSummary?.(merged.contextSummary, { legacy: merged.compaction === true });
5993
+ }
5994
+ }
5765
5995
  if (isTerminalStatus(merged.status) && merged.segments) {
5766
5996
  for (const id of openSegmentIds(merged.segments)) {
5767
5997
  this.updateSegment(id, { isStreaming: false });
@@ -6060,18 +6290,20 @@ class AparteChatHost {
6060
6290
  /**
6061
6291
  * Reconcile rendered bubbles with the current message list. The framework
6062
6292
  * calls this from its reactive message-change hook after rendering; the
6063
- * last message is always re-synced (streaming), others only once.
6293
+ * last message is always re-synced (streaming), the others whenever the list
6294
+ * holds a different object for them than the one they were drawn from.
6064
6295
  */
6065
6296
  syncBubbles() {
6066
6297
  const msgs = this.binding.getMessages();
6067
6298
  msgs.forEach((message, index) => {
6068
6299
  const isLast = index === msgs.length - 1;
6069
- if (this._renderedIds.has(message.id) && !isLast) return;
6300
+ if (this._renderedIds.get(message.id) === message && !isLast) return;
6070
6301
  const bubble = this._bubbleById(message.id);
6071
6302
  if (!bubble) return;
6072
6303
  populateBubbleFromMessage(bubble, message);
6073
- this._renderedIds.add(message.id);
6304
+ this._renderedIds.set(message.id, message);
6074
6305
  });
6306
+ this._checkpointState.reapply();
6075
6307
  }
6076
6308
  /**
6077
6309
  * Rebuild the viewport's internal repo from the message list before branch
@@ -6190,6 +6422,7 @@ class AparteChatHost {
6190
6422
  host2["removeSegment"] = (id) => this.removeSegment(id);
6191
6423
  host2["appendToSegment"] = (id, c) => this.appendToSegment(id, c);
6192
6424
  host2["getMessages"] = () => this.getMessages();
6425
+ host2["getMessage"] = (id) => this.binding.getMessages().find((m) => m.id === id);
6193
6426
  host2["addBranch"] = (id) => this.addBranch(id);
6194
6427
  host2["addSiblingOf"] = (id, m) => this.addSiblingOf(id, m);
6195
6428
  host2["truncateFrom"] = (id) => this.truncateFrom(id);
@@ -6264,6 +6497,37 @@ class AparteChatHost {
6264
6497
  });
6265
6498
  }
6266
6499
  }
6500
+ function historyFromCheckpoint(path) {
6501
+ for (let i = path.length - 1; i >= 0; i--) {
6502
+ const summary = path[i].contextSummary?.summary;
6503
+ if (summary !== void 0) return { summary, messages: path.slice(i) };
6504
+ }
6505
+ return { messages: path };
6506
+ }
6507
+ const COMPACTION_PREAMBLE = "The earlier part of this conversation was compacted. This is a summary of what came before; continue from it.";
6508
+ const underPreamble = (text) => `${COMPACTION_PREAMBLE}
6509
+
6510
+ ${text}`;
6511
+ function mergeAdjacentUserMessages(messages) {
6512
+ const asParts = (c) => typeof c === "string" ? [{ type: "text", text: c }] : c;
6513
+ const out = [];
6514
+ for (const m of messages) {
6515
+ const prev = out[out.length - 1];
6516
+ if (m.role !== "user" || prev?.role !== "user") {
6517
+ out.push(m);
6518
+ } else if (typeof prev.content === "string" && typeof m.content === "string") {
6519
+ out[out.length - 1] = { ...prev, content: `${prev.content}
6520
+
6521
+ ${m.content}` };
6522
+ } else {
6523
+ out[out.length - 1] = {
6524
+ ...prev,
6525
+ content: [...asParts(prev.content), { type: "text", text: "\n\n" }, ...asParts(m.content)]
6526
+ };
6527
+ }
6528
+ }
6529
+ return out;
6530
+ }
6267
6531
  const STOPPED_TOOL_TEXT = "The tool call was stopped before it ran.";
6268
6532
  const EMPTY_TOOL_TEXT = "The tool returned no output.";
6269
6533
  function answerOf(segment) {
@@ -6314,6 +6578,139 @@ function wireTurns(message, textOf, options = {}) {
6314
6578
  flush();
6315
6579
  return out;
6316
6580
  }
6581
+ const isInFlight = (m) => m.status === "streaming" || m.status === "pending";
6582
+ function segmentsToWireText(segments) {
6583
+ if (!segments?.length) return "";
6584
+ const fence = "```";
6585
+ const parts = [];
6586
+ for (const segment of segments) {
6587
+ const content = segment.content;
6588
+ if (segment.type === "text") {
6589
+ if (content) parts.push(content);
6590
+ } else if (segment.type === "code") {
6591
+ const lang = segment.language ?? "";
6592
+ parts.push(`${fence}${lang}
6593
+ ${content ?? ""}
6594
+ ${fence}`);
6595
+ } else if (segment.type === "thinking" || segment.type === "tool_call" || segment.type === "error") {
6596
+ continue;
6597
+ } else if (typeof content === "string" && content) {
6598
+ parts.push(content);
6599
+ } else {
6600
+ const fallback = segment.fallback;
6601
+ if (fallback) parts.push(fallback);
6602
+ }
6603
+ }
6604
+ return parts.join("\n").trim();
6605
+ }
6606
+ function extractWireText(message) {
6607
+ const rendered = segmentsToWireText(message.segments);
6608
+ if (rendered) return rendered;
6609
+ if (typeof message.content === "string" && message.content) return message.content;
6610
+ return "";
6611
+ }
6612
+ function messageWireText(message) {
6613
+ const text = extractWireText(message);
6614
+ return message.compaction ? underPreamble(text) : text;
6615
+ }
6616
+ function wireMessages(messages) {
6617
+ return messages.filter((m) => {
6618
+ if (m.role !== "user" && m.role !== "assistant") return false;
6619
+ return m.status !== "error" && !isInFlight(m);
6620
+ }).flatMap((m) => {
6621
+ if (m.role === "assistant" && m.segments?.some((s) => s.type === "tool_call")) {
6622
+ return wireTurns(m, segmentsToWireText);
6623
+ }
6624
+ const content = messageWireText(m);
6625
+ return content ? [{ role: m.role, content }] : [];
6626
+ });
6627
+ }
6628
+ function estimateTokens(text) {
6629
+ if (!text) return 0;
6630
+ return Math.ceil(text.length / 3.8);
6631
+ }
6632
+ const estimateJson = (value) => {
6633
+ if (!value) return 0;
6634
+ try {
6635
+ return estimateTokens(JSON.stringify(value));
6636
+ } catch {
6637
+ return 0;
6638
+ }
6639
+ };
6640
+ function estimateChatTokens(messages) {
6641
+ let total = 0;
6642
+ for (const message of messages) {
6643
+ total += estimateTokens(contentToText(message.content));
6644
+ if (message.toolCalls?.length) total += estimateJson(message.toolCalls);
6645
+ }
6646
+ return total;
6647
+ }
6648
+ function carriesNonTextParts(messages) {
6649
+ return messages.some((m) => typeof m.content !== "string" && m.content.some((p) => p.type !== "text"));
6650
+ }
6651
+ function estimateRequestTokens(request) {
6652
+ const declared = (request.tools ?? []).map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));
6653
+ return estimateChatTokens(request.messages) + (declared.length ? estimateJson(declared) : 0);
6654
+ }
6655
+ function estimateWireTokens(messages) {
6656
+ return estimateChatTokens(wireMessages(messages));
6657
+ }
6658
+ const usageTokens = (usage) => usage.totalTokens ?? usage.inputTokens + usage.outputTokens;
6659
+ function toolsForCurrentModel(config) {
6660
+ const capabilities = config.getCurrentModel()?.capabilities;
6661
+ const declinesTools = capabilities ? !capabilities.includes("function_calling") : false;
6662
+ return declinesTools ? [] : config.getTools();
6663
+ }
6664
+ function windowOf(config, windowOverride) {
6665
+ if (typeof windowOverride === "number" && Number.isFinite(windowOverride) && windowOverride > 0) return windowOverride;
6666
+ const declared = config.getCurrentModel()?.contextWindow;
6667
+ return declared && declared > 0 ? declared : void 0;
6668
+ }
6669
+ const hasSegment = (message, type) => message.segments?.some((s) => s.type === type) === true;
6670
+ function calibration(reply) {
6671
+ const sent = reply.usage?.inputTokens;
6672
+ const recorded = reply.contextEstimate;
6673
+ if (hasSegment(reply, "tool_call")) return 1;
6674
+ return sent && recorded && sent > 0 && recorded > 0 ? Math.min(3, Math.max(0.5, sent / recorded)) : 1;
6675
+ }
6676
+ function nextRequestTokens(reply, k) {
6677
+ const usage = reply.usage;
6678
+ if (!hasSegment(reply, "thinking") || !(usage.inputTokens > 0)) return usageTokens(usage);
6679
+ const segments = reply.segments ?? [];
6680
+ const lastCall = segments.map((s) => s.type).lastIndexOf("tool_call");
6681
+ const said = Math.round(k * estimateTokens(segmentsToWireText(segments.slice(lastCall + 1))));
6682
+ return usage.inputTokens + (usage.outputTokens > 0 ? Math.min(said, usage.outputTokens) : said);
6683
+ }
6684
+ function measureContext(input) {
6685
+ const { messages, config } = input;
6686
+ const view = historyFromCheckpoint(messages);
6687
+ const system = estimateTokens(config.resolveSystemPrompt()) + estimateTokens(config.resolveToolSystemPrompts());
6688
+ const declared = toolsForCurrentModel(config).map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema }));
6689
+ const tools = declared.length ? estimateJson(declared) : 0;
6690
+ const summary = view.summary === void 0 ? 0 : estimateTokens(underPreamble(view.summary));
6691
+ const parts = { system, tools, summary, messages: estimateWireTokens(view.messages) };
6692
+ const total = system + tools + summary + parts.messages;
6693
+ let replyIndex = -1;
6694
+ for (let i = messages.length - 1; i >= 0; i--) {
6695
+ if (messages[i].role === "assistant" && messages[i].usage) {
6696
+ replyIndex = i;
6697
+ break;
6698
+ }
6699
+ }
6700
+ const reply = replyIndex >= 0 ? messages[replyIndex] : void 0;
6701
+ const window2 = windowOf(config, input.windowOverride);
6702
+ const result = (used, estimated) => ({
6703
+ ...window2 !== void 0 ? { window: window2 } : {},
6704
+ used,
6705
+ estimated,
6706
+ parts,
6707
+ other: Math.max(0, used - total)
6708
+ });
6709
+ const unchanged = reply !== void 0 && !input.viewChanged && replyIndex === messages.length - 1 && !messages.some((m) => m.contextSummary !== void 0 && m.contextSummary.createdAt > reply.timestamp);
6710
+ const k = reply ? calibration(reply) : 1;
6711
+ if (reply && unchanged) return result(nextRequestTokens(reply, k), false);
6712
+ return result(Math.round(k * total), true);
6713
+ }
6317
6714
  function eventOrigin(e) {
6318
6715
  return e.composedPath?.()[0] ?? e.target;
6319
6716
  }
@@ -6850,7 +7247,6 @@ function buildApprovalPanel(message, options, onChange, details) {
6850
7247
  function requestUserInput(request) {
6851
7248
  return resolveConfig(request.target ?? null).requestUserInput(request);
6852
7249
  }
6853
- const COMPACTION_PREAMBLE = "The earlier part of this conversation was compacted. This is a summary of what came before; continue from it.";
6854
7250
  const NOT_RUN_AFTER_A_REFUSAL = "Not run: an earlier call of the same step was refused.";
6855
7251
  const approvalKey = (call) => JSON.stringify([call.id, call.name, call.input]);
6856
7252
  const warnedUninlinedFiles = /* @__PURE__ */ new Set();
@@ -6879,6 +7275,28 @@ class AparteClient {
6879
7275
  _boundEditHandler = null;
6880
7276
  _releaseKeyResolver = null;
6881
7277
  _isAborted = false;
7278
+ /** Provider and model pairs whose window was already read again after a turn (see `_readWindowAfterTurn`). */
7279
+ _windowReadFor = /* @__PURE__ */ new Set();
7280
+ /**
7281
+ * The current model's window, read again once after a turn when it had none.
7282
+ *
7283
+ * A local server that loads a model on its first request — LM Studio's just-in-time
7284
+ * loading, Ollama — has no loaded window to report when the model list is fetched at
7285
+ * start-up, so the model comes without one, and the gauge and the compaction have
7286
+ * nothing to measure against. After a reply the model is loaded, and the provider's
7287
+ * own probe finds it. Once per provider and model: a server that never says costs one
7288
+ * request, not one per turn. The config announces the window when it appears.
7289
+ */
7290
+ async _readWindowAfterTurn() {
7291
+ const config = this._config;
7292
+ const model = config.getCurrentModel();
7293
+ const providerId = config.getModelConfig().defaultProvider;
7294
+ if (!model || model.contextWindow || !providerId) return;
7295
+ const key = `${providerId}\0${model.id}`;
7296
+ if (this._windowReadFor.has(key)) return;
7297
+ this._windowReadFor.add(key);
7298
+ await config.refreshProviderModels(providerId);
7299
+ }
6882
7300
  /**
6883
7301
  * Does this window event belong to this client?
6884
7302
  *
@@ -7160,19 +7578,23 @@ class AparteClient {
7160
7578
  async _streamTurn(targetElement, messageId, provider, messages, modelId, authConfig, opts) {
7161
7579
  const chatId = this._targetIdOf(targetElement);
7162
7580
  const registeredTools = this._toolsForCurrentModel();
7163
- let baseRequest = {
7164
- messages,
7165
- modelId,
7166
- stream: true,
7167
- tools: registeredTools.length ? registeredTools : void 0,
7168
- ...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {}
7581
+ const buildRequest = async (requestMessages2) => {
7582
+ let built = {
7583
+ messages: requestMessages2,
7584
+ modelId,
7585
+ stream: true,
7586
+ tools: registeredTools.length ? registeredTools : void 0,
7587
+ ...opts?.temperature !== void 0 ? { temperature: opts.temperature } : {}
7588
+ };
7589
+ if (this.options.requestInterceptor) {
7590
+ built = await this.options.requestInterceptor(built);
7591
+ }
7592
+ if (built.toolChoice === "none") {
7593
+ built = { ...built, tools: void 0 };
7594
+ }
7595
+ return built;
7169
7596
  };
7170
- if (this.options.requestInterceptor) {
7171
- baseRequest = await this.options.requestInterceptor(baseRequest);
7172
- }
7173
- if (baseRequest.toolChoice === "none") {
7174
- baseRequest = { ...baseRequest, tools: void 0 };
7175
- }
7597
+ let baseRequest = await buildRequest(messages);
7176
7598
  if (this._isAborted) {
7177
7599
  dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
7178
7600
  this._updateMessage(targetElement, messageId, { status: "completed" });
@@ -7193,7 +7615,23 @@ class AparteClient {
7193
7615
  if (this._streamController !== streamController) this._streamController?.abort();
7194
7616
  this._streamController = streamController;
7195
7617
  this._turnsInFlight.add(messageId);
7196
- try {
7618
+ const compactor = opts?.rebuild ? resolveConfig(targetElement).getCompactor() ?? this._config.getCompactor() : null;
7619
+ const turnIds = () => {
7620
+ const path = targetElement.getMessages?.() ?? [];
7621
+ const at = path.findIndex((m) => m.id === messageId);
7622
+ const asked = at > 0 ? path[at - 1] : void 0;
7623
+ return asked?.role === "user" ? [asked.id, messageId] : [messageId];
7624
+ };
7625
+ const requestMessages = () => opts?.resume ? this._resumeMessages(targetElement, messageId, baseRequest.messages) : baseRequest.messages;
7626
+ const requestEstimate = () => estimateRequestTokens({ messages: requestMessages(), tools: baseRequest.tools });
7627
+ const recordedEstimate = () => carriesNonTextParts(requestMessages()) ? void 0 : requestEstimate();
7628
+ const endStopped = () => {
7629
+ this._closeOpenCalls(targetElement, messageId);
7630
+ dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
7631
+ this._updateMessage(targetElement, messageId, { status: "completed" });
7632
+ };
7633
+ const isStopped = () => this._isAborted || streamController.signal.aborted;
7634
+ const runOnce = async () => {
7197
7635
  let result = opts?.resume ? { usage: void 0, suspended: opts.resume } : await this._streamLoop(targetElement, messageId, provider, baseRequest, authConfig, streamController);
7198
7636
  const maxResumes = this.options.maxTurns ?? 10;
7199
7637
  let resumes = 0;
@@ -7236,21 +7674,80 @@ class AparteClient {
7236
7674
  );
7237
7675
  }
7238
7676
  const usage = result.usage;
7677
+ const finishReason = result.finishReason;
7239
7678
  if (this._isAborted || streamController.signal.aborted) {
7240
7679
  this._closeOpenCalls(targetElement, messageId);
7241
7680
  this._updateMessage(targetElement, messageId, { status: "completed" });
7242
7681
  } else {
7243
- dispatchLifecycleEvent(targetElement, "aparte-message-done", { messageId, role: "assistant", usage }, chatId);
7682
+ dispatchLifecycleEvent(targetElement, "aparte-message-done", { messageId, role: "assistant", usage, ...finishReason ? { finishReason } : {} }, chatId);
7683
+ void this._readWindowAfterTurn();
7244
7684
  }
7245
- } catch (error) {
7246
- this._closeOpenCalls(targetElement, messageId);
7247
- if (this._isAborted || streamController.signal.aborted) {
7248
- dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
7249
- this._updateMessage(targetElement, messageId, { status: "completed" });
7250
- return;
7685
+ };
7686
+ const resendAfterOverflow = async (error) => {
7687
+ if (error.code !== AparteErrorCode.USAGE_CONTEXT_EXCEEDED || !compactor || !opts?.rebuild) return "stands";
7688
+ const segments = targetElement.getMessage?.(messageId)?.segments ?? targetElement.getMessages?.()?.find((m) => m.id === messageId)?.segments ?? [];
7689
+ if (segments.some((s) => s.type === "text" || s.type === "tool_call")) return "stands";
7690
+ try {
7691
+ const outcome = await compactor.compact({
7692
+ target: targetElement,
7693
+ ...chatId ? { targetId: chatId } : {},
7694
+ force: true,
7695
+ reason: "overflow",
7696
+ exclude: turnIds(),
7697
+ signal: streamController.signal
7698
+ });
7699
+ if (isStopped()) return "stopped";
7700
+ if (!outcome.ok || outcome.skipped) return "stands";
7701
+ baseRequest = await buildRequest(await opts.rebuild());
7702
+ } catch {
7703
+ return "stands";
7704
+ }
7705
+ this._updateMessage(targetElement, messageId, { status: "pending" });
7706
+ return "resend";
7707
+ };
7708
+ try {
7709
+ for (let attempt = 0; ; attempt++) {
7710
+ try {
7711
+ if (attempt === 0 && compactor?.beforeSend && opts?.rebuild) {
7712
+ let compacted = false;
7713
+ try {
7714
+ compacted = await compactor.beforeSend({
7715
+ target: targetElement,
7716
+ ...chatId ? { targetId: chatId } : {},
7717
+ exclude: turnIds(),
7718
+ requestTokens: requestEstimate(),
7719
+ signal: streamController.signal
7720
+ });
7721
+ } catch (failure) {
7722
+ console.warn("[AparteClient] The compactor threw before a send; sending the request as built.", failure);
7723
+ }
7724
+ if (isStopped()) {
7725
+ endStopped();
7726
+ return;
7727
+ }
7728
+ if (compacted) baseRequest = await buildRequest(await opts.rebuild());
7729
+ }
7730
+ this._updateMessage(targetElement, messageId, { contextEstimate: recordedEstimate() });
7731
+ await runOnce();
7732
+ return;
7733
+ } catch (error) {
7734
+ this._closeOpenCalls(targetElement, messageId);
7735
+ if (isStopped()) {
7736
+ dispatchLifecycleEvent(targetElement, "aparte-message-aborted", { messageId }, chatId);
7737
+ this._updateMessage(targetElement, messageId, { status: "completed" });
7738
+ return;
7739
+ }
7740
+ const aparteError = AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR);
7741
+ const next = attempt === 0 ? await resendAfterOverflow(aparteError) : "stands";
7742
+ if (next === "resend") continue;
7743
+ if (next === "stopped") {
7744
+ endStopped();
7745
+ return;
7746
+ }
7747
+ this._handleLifecycleError(targetElement, messageId, aparteError);
7748
+ return;
7749
+ }
7251
7750
  }
7252
- const aparteError = AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR);
7253
- this._handleLifecycleError(targetElement, messageId, aparteError);
7254
7751
  } finally {
7255
7752
  this._turnsInFlight.delete(messageId);
7256
7753
  }
@@ -7265,29 +7762,12 @@ class AparteClient {
7265
7762
  * using the same conversation history minus the retried reply.
7266
7763
  */
7267
7764
  /**
7268
- * The registered tools, unless the current model says it cannot call one.
7269
- *
7270
- * Single source for the gate, so send / retry / edit cannot drift.
7271
- *
7272
- * The question is "did this model say it cannot", not "did it say it can": the
7273
- * app sets a model id by hand (`setModelConfig`) or a selector sets it from a
7274
- * fetched list, and in both cases the model's declared capabilities are commonly
7275
- * unknown. `undefined` capabilities read as "can", or `tools: []` would go on
7276
- * the wire while `getTools()` held the tool the app had explicitly registered,
7277
- * and the model would answer that it had no such tool. Registering a tool is an explicit act by the app; silently dropping it
7278
- * because a `/models` listing is terse is the library second-guessing the
7279
- * developer. A model that declares its capabilities and omits function calling
7280
- * is still honoured — that is a statement, and it is respected.
7281
- *
7282
- * Failure modes, both ways round: over-sending means a model that cannot use
7283
- * tools does not call one, or its own endpoint rejects the array with an error
7284
- * the developer can read. Under-sending would be silent, total, and look like a
7285
- * lying model.
7765
+ * The registered tools, unless the current model says it cannot call one — the gate
7766
+ * lives in `toolsForCurrentModel` (with its reasoning), where the context measure reads
7767
+ * the same rule, so send / retry / edit and the gauge cannot drift.
7286
7768
  */
7287
7769
  _toolsForCurrentModel() {
7288
- const capabilities = this._config.getCurrentModel()?.capabilities;
7289
- const declinesTools = capabilities ? !capabilities.includes("function_calling") : false;
7290
- return declinesTools ? [] : this._config.getTools();
7770
+ return toolsForCurrentModel(this._config);
7291
7771
  }
7292
7772
  async _handleRetry(event) {
7293
7773
  const { messageId, targetId } = event.detail ?? {};
@@ -7322,10 +7802,16 @@ class AparteClient {
7322
7802
  targetElement,
7323
7803
  newMessageId,
7324
7804
  provider,
7325
- [...systemMessages, ...chatMessages],
7805
+ [...systemMessages, ...mergeAdjacentUserMessages(chatMessages)],
7326
7806
  config.defaultModel || "",
7327
7807
  authConfig,
7328
- { temperature: 0.4 }
7808
+ {
7809
+ temperature: 0.4,
7810
+ rebuild: async () => [
7811
+ ...this._systemMessages(),
7812
+ ...mergeAdjacentUserMessages(this._messagesToChatMessages(this._current(targetElement, historyMessages)))
7813
+ ]
7814
+ }
7329
7815
  );
7330
7816
  }
7331
7817
  /**
@@ -7388,11 +7874,27 @@ class AparteClient {
7388
7874
  targetElement,
7389
7875
  newMessageId,
7390
7876
  provider,
7391
- [...systemMessages, ...chatMessages],
7877
+ [...systemMessages, ...mergeAdjacentUserMessages(chatMessages)],
7392
7878
  config.defaultModel || "",
7393
- authConfig
7879
+ authConfig,
7880
+ {
7881
+ rebuild: async () => [
7882
+ ...this._systemMessages(),
7883
+ ...mergeAdjacentUserMessages(this._messagesToChatMessages(this._current(targetElement, historyMessages)))
7884
+ ]
7885
+ }
7394
7886
  );
7395
7887
  }
7888
+ /**
7889
+ * These messages as the transcript holds them NOW. A retry or an edit slices the path
7890
+ * before it streams; a compaction that runs during the turn writes its checkpoint onto
7891
+ * one of those messages in the transcript, and the slice still holds the objects as they
7892
+ * were. Looked up by id, tree-wide, because a retry has moved the path since.
7893
+ */
7894
+ _current(target, messages) {
7895
+ const live2 = new Map((target.getMessages?.() ?? []).map((m) => [m.id, m]));
7896
+ return messages.map((m) => target.getMessage?.(m.id) ?? live2.get(m.id) ?? m);
7897
+ }
7396
7898
  /**
7397
7899
  * The chain the gesture really came up, nearest node first.
7398
7900
  *
@@ -7526,29 +8028,17 @@ class AparteClient {
7526
8028
  * imitating.
7527
8029
  */
7528
8030
  _messagesToChatMessages(messages) {
7529
- return this._wireHistory(messages);
8031
+ const { summary, messages: kept } = historyFromCheckpoint(messages);
8032
+ return [...this._summaryMessage(summary), ...wireMessages(kept)];
7530
8033
  }
7531
8034
  /**
7532
- * The one rule for what a message contributes to the wire.
7533
- *
7534
- * Not the slice — the contribution: a role the wire has (`user`/`assistant`), a
7535
- * turn that neither failed nor is still in flight, and text that is not empty.
7536
- *
7537
- * `_wireText` (not `m.content`): assistant replies stream their text into
7538
- * `segments`, leaving `content` as '', so without flattening every reply would be
7539
- * empty here and the drop below would swallow the whole transcript.
7540
- */
7541
- _wireHistory(messages) {
7542
- return messages.filter((m) => {
7543
- if (m.role !== "user" && m.role !== "assistant") return false;
7544
- return m.status !== "error" && !AparteClient._inFlight(m);
7545
- }).flatMap((m) => {
7546
- if (m.role === "assistant" && m.segments?.some((s) => s.type === "tool_call")) {
7547
- return wireTurns(m, (segments) => this._segmentsToText(segments));
7548
- }
7549
- const content = this._wireText(m);
7550
- return content ? [{ role: m.role, content }] : [];
7551
- });
8035
+ * What the model reads in place of everything before a checkpoint: the summary, as a
8036
+ * `user` message under the same preamble a legacy compaction message gets, so it is
8037
+ * context and not something the person said. It may sit right before a user turn;
8038
+ * `mergeAdjacentUserMessages` joins the two where the history is assembled.
8039
+ */
8040
+ _summaryMessage(summary) {
8041
+ return summary === void 0 ? [] : [{ role: "user", content: underPreamble(summary) }];
7552
8042
  }
7553
8043
  /** The "no model selected" warning is said once per client, not once per dropped send. */
7554
8044
  _warnedNoModel = false;
@@ -7638,12 +8128,14 @@ class AparteClient {
7638
8128
  });
7639
8129
  let authConfig;
7640
8130
  let messages;
8131
+ let fileParts;
7641
8132
  try {
7642
8133
  authConfig = providerId ? await this._resolveAuth(providerId) : void 0;
7643
8134
  const rawFiles = Array.isArray(event.detail?.files) ? event.detail.files : [];
7644
8135
  const filesToInject = this._selectFilesToInject(rawFiles);
7645
8136
  const contentParts = filesToInject.length > 0 ? await this._filesToContentParts(filesToInject) : [];
7646
- messages = this._buildMessages(content, targetElement, contentParts.length > 0 ? contentParts : void 0);
8137
+ fileParts = contentParts.length > 0 ? contentParts : void 0;
8138
+ messages = this._buildMessages(content, targetElement, fileParts);
7647
8139
  } catch (error) {
7648
8140
  this._handleLifecycleError(targetElement, messageId, AparteError.from(error, AparteErrorCode.UNKNOWN_ERROR));
7649
8141
  return;
@@ -7654,7 +8146,8 @@ class AparteClient {
7654
8146
  provider,
7655
8147
  messages,
7656
8148
  modelId || config.defaultModel || "",
7657
- authConfig
8149
+ authConfig,
8150
+ { rebuild: async () => this._buildMessages(content, targetElement, fileParts) }
7658
8151
  );
7659
8152
  }
7660
8153
  /**
@@ -7671,101 +8164,18 @@ class AparteClient {
7671
8164
  }
7672
8165
  const userMsg = parts && parts.length > 0 ? [{ type: "text", text: userContent }, ...parts] : userContent;
7673
8166
  messages.push({ role: "user", content: userMsg });
7674
- return messages;
8167
+ return mergeAdjacentUserMessages(messages);
7675
8168
  }
7676
- /**
7677
- * A turn that has not finished yet. `status` is OPTIONAL on `AparteMessage`, so
7678
- * the question the history gates can ask is "is this still in flight?", never
7679
- * "is this completed?".
7680
- *
7681
- * The gates below ask the first, because a host that SEEDS a transcript
7682
- * — restoring a saved conversation, hydrating a server-rendered one — has no
7683
- * reason to invent a status for turns that are already over. Asking "is this
7684
- * completed?" would leave the cutoff at 0 and slice away the whole transcript: the
7685
- * model would get the new question with no context, and nothing in the UI would say
7686
- * so, because the viewport still shows every message.
7687
- */
7688
- static _inFlight(m) {
7689
- return m.status === "streaming" || m.status === "pending";
7690
- }
7691
- _toHistoryMessages(messages) {
8169
+ _toHistoryMessages(path) {
8170
+ const { summary, messages } = historyFromCheckpoint(path);
7692
8171
  let cutoff = 0;
7693
8172
  for (let i = 0; i < messages.length; i++) {
7694
8173
  const m = messages[i];
7695
- if (m.role === "assistant" && m.status !== "error" && !AparteClient._inFlight(m)) {
8174
+ if (m.role === "assistant" && m.status !== "error" && !isInFlight(m)) {
7696
8175
  cutoff = i + 1;
7697
8176
  }
7698
8177
  }
7699
- return this._wireHistory(messages.slice(0, cutoff));
7700
- }
7701
- /**
7702
- * A message's text as the MODEL should read it — the one mapper both history
7703
- * paths use, so the compaction preamble cannot be applied on retry and edit and
7704
- * forgotten on the ordinary send, which is what the first review of this feature
7705
- * caught: the summary went out on every normal turn as something the user said.
7706
- */
7707
- _wireText(message) {
7708
- const text = this._extractText(message);
7709
- return message.compaction ? `${COMPACTION_PREAMBLE}
7710
-
7711
- ${text}` : text;
7712
- }
7713
- /**
7714
- * What the model is told it said last turn — reconstructed from what was
7715
- * RENDERED, not from the raw-text field.
7716
- *
7717
- * The segments are what the reader saw, so they come first. `content` is the
7718
- * fallback, and that is not vestigial: a non-streaming transport writes the whole
7719
- * reply there and creates no segments at all.
7720
- */
7721
- _extractText(message) {
7722
- const rendered = this._segmentsToText(message.segments);
7723
- if (rendered) return rendered;
7724
- if (typeof message.content === "string" && message.content) return message.content;
7725
- return "";
7726
- }
7727
- /**
7728
- * Segments → the text a model can read back.
7729
- *
7730
- * Fences and the language tag are kept. Without them the model re-reads its own
7731
- * code as prose — which is how it starts explaining a snippet it believes it
7732
- * wrote in English.
7733
- *
7734
- * A type core does not know — a registered block grammar's (an artifact, a
7735
- * citation, a file), a consumer's custom segment — follows one rule: its
7736
- * `content` if it has one, else its `fallback`, else nothing. Dropping such a
7737
- * segment would make a document the model had just produced invisible on the very
7738
- * next turn, so it could not be asked to change the thing it had built. An artifact
7739
- * is a plugin's type like any other and follows this rule.
7740
- *
7741
- * `thinking` and `tool_call` stay out on purpose — the first is the model's own
7742
- * scratchpad, and most APIs neither want it back nor bill for it kindly; the
7743
- * second is not text: `_wireHistory` sends a reply's calls as the assistant's
7744
- * `toolCalls` and the `tool` messages answering them (`wire-history.ts`).
7745
- */
7746
- _segmentsToText(segments) {
7747
- if (!segments?.length) return "";
7748
- const fence = "```";
7749
- const parts = [];
7750
- for (const segment of segments) {
7751
- const content = segment.content;
7752
- if (segment.type === "text") {
7753
- if (content) parts.push(content);
7754
- } else if (segment.type === "code") {
7755
- const lang = segment.language ?? "";
7756
- parts.push(`${fence}${lang}
7757
- ${content ?? ""}
7758
- ${fence}`);
7759
- } else if (segment.type === "thinking" || segment.type === "tool_call" || segment.type === "error") {
7760
- continue;
7761
- } else if (typeof content === "string" && content) {
7762
- parts.push(content);
7763
- } else {
7764
- const fallback = segment.fallback;
7765
- if (fallback) parts.push(fallback);
7766
- }
7767
- }
7768
- return parts.join("\n").trim();
8178
+ return [...this._summaryMessage(summary), ...wireMessages(messages.slice(0, cutoff))];
7769
8179
  }
7770
8180
  /**
7771
8181
  * Which of the send's pending files get inlined into the request: the
@@ -7872,8 +8282,10 @@ ${content}
7872
8282
  });
7873
8283
  const prefixSegments = baseRequest._meta?.["prefixSegments"];
7874
8284
  let suspended;
8285
+ let finishReason;
7875
8286
  const emitter = (event) => {
7876
8287
  adapter(event);
8288
+ if (event.type === "run-done") finishReason = event.finishReason;
7877
8289
  if (event.type === "run-suspended") {
7878
8290
  const ticket = event.ticket;
7879
8291
  suspended = { pending: event.pending, ...typeof ticket === "string" ? { ticket } : {} };
@@ -7926,7 +8338,11 @@ ${content}
7926
8338
  // BARE uuid for the synthetic tool (the adapter renders `tool-<id>`).
7927
8339
  idGen: (prefix) => prefix === "synthetic-tool" ? uuid() : `${prefix}-${uuid()}`
7928
8340
  });
7929
- return { usage: usage ?? void 0, ...suspended ? { suspended } : {} };
8341
+ return {
8342
+ usage: usage ?? void 0,
8343
+ ...finishReason ? { finishReason } : {},
8344
+ ...suspended ? { suspended } : {}
8345
+ };
7930
8346
  }
7931
8347
  /**
7932
8348
  * The approval channel a run asks through — and the one a paused server run's calls
@@ -8077,7 +8493,7 @@ ${content}
8077
8493
  */
8078
8494
  _resumeMessages(targetElement, messageId, sent) {
8079
8495
  const reply = targetElement.getMessages?.().find((m) => m.id === messageId);
8080
- return [...sent, ...reply ? wireTurns(reply, (segments) => this._segmentsToText(segments), { openDeferred: true }) : []];
8496
+ return [...sent, ...reply ? wireTurns(reply, segmentsToWireText, { openDeferred: true }) : []];
8081
8497
  }
8082
8498
  /**
8083
8499
  * What the send of a reloaded turn put before its reply, rebuilt the way
@@ -8092,7 +8508,11 @@ ${content}
8092
8508
  const askedAt = before.map((m) => m.role).lastIndexOf("user");
8093
8509
  const historyOption = this.options.history ?? "viewport";
8094
8510
  const history = historyOption === "viewport" ? this._toHistoryMessages(before) : typeof historyOption === "function" ? historyOption(before) : [];
8095
- return [...this._systemMessages(), ...history, ...askedAt >= 0 ? this._wireHistory([before[askedAt]]) : []];
8511
+ return mergeAdjacentUserMessages([
8512
+ ...this._systemMessages(),
8513
+ ...history,
8514
+ ...askedAt >= 0 ? wireMessages([before[askedAt]]) : []
8515
+ ]);
8096
8516
  }
8097
8517
  /**
8098
8518
  * A new message closes a wait nobody answered. The paused calls of the last reply are
@@ -8153,6 +8573,11 @@ ${content}
8153
8573
  const base = this._resumeBase(messages, messages.indexOf(last));
8154
8574
  await this._streamTurn(targetElement, last.id, provider, base, recorded?.modelId ?? config.defaultModel ?? "", authConfig, {
8155
8575
  resume: { pending: pending2, ticket },
8576
+ rebuild: async () => {
8577
+ const now = targetElement.getMessages?.() ?? [];
8578
+ const at = now.findIndex((m) => m.id === last.id);
8579
+ return at >= 0 ? this._resumeBase(now, at) : base;
8580
+ },
8156
8581
  ...recorded?.temperature !== void 0 ? { temperature: recorded.temperature } : {}
8157
8582
  });
8158
8583
  } finally {
@@ -8232,7 +8657,8 @@ function createAgentStreamRunner(options) {
8232
8657
  let ended = false;
8233
8658
  for await (const line of readNdjson(response.body, opts.signal)) {
8234
8659
  if (line.type === "run-error") {
8235
- throw new AparteError(line.message || "The agent run failed.", AparteErrorCode.PROVIDER_ERROR, line.code ? { code: line.code } : void 0);
8660
+ const known2 = Object.values(AparteErrorCode).includes(line.code ?? "") ? line.code : void 0;
8661
+ throw new AparteError(line.message || "The agent run failed.", known2 ?? AparteErrorCode.PROVIDER_ERROR, line.code ? { code: line.code } : void 0);
8236
8662
  }
8237
8663
  if (line.type === "run-done" || line.type === "run-suspended") {
8238
8664
  usage = line.usage;
@@ -8727,7 +9153,7 @@ function deferHydrationOf(ctor) {
8727
9153
  };
8728
9154
  }
8729
9155
  export {
8730
- parseMarkdownToSegments as $,
9156
+ isSegmentSettled as $,
8731
9157
  APARTE_CONFIG_CHANGE as A,
8732
9158
  contextConfig as B,
8733
9159
  copyText as C,
@@ -8744,57 +9170,64 @@ export {
8744
9170
  detachConfig as N,
8745
9171
  escapeAttr as O,
8746
9172
  escapeHtml as P,
8747
- filesToAttachments as Q,
8748
- getAllRenderers as R,
8749
- getSegmentRenderer as S,
8750
- installDefaultRenderersOnce as T,
8751
- installDialogTriggersOnce as U,
8752
- isAwaitingReply as V,
8753
- isFormatAdapter as W,
8754
- isSafeUrl as X,
8755
- isSegmentSettled as Y,
8756
- overrideSegmentRenderer as Z,
8757
- parseAparteEventStream as _,
9173
+ estimateTokens as Q,
9174
+ estimateWireTokens as R,
9175
+ filesToAttachments as S,
9176
+ getAllRenderers as T,
9177
+ getSegmentRenderer as U,
9178
+ historyFromCheckpoint as V,
9179
+ installDefaultRenderersOnce as W,
9180
+ installDialogTriggersOnce as X,
9181
+ isAwaitingReply as Y,
9182
+ isFormatAdapter as Z,
9183
+ isSafeUrl as _,
8758
9184
  APARTE_CONVERSATION_SCHEMA_VERSION as a,
8759
- populateBubbleFromMessage as a0,
8760
- readableToAsyncIterable as a1,
8761
- registerDefaultRenderers as a2,
8762
- registerSegmentRenderer as a3,
8763
- releaseDeferredHydration as a4,
8764
- requestUserInput as a5,
8765
- resolveConfig as a6,
8766
- revokeAttachmentUrls as a7,
8767
- runWithConfig as a8,
8768
- segmentDuration as a9,
8769
- conversationStatusOf as aA,
8770
- chatBoundaryOf as aB,
8771
- rootOf as aC,
8772
- segmentTiming as aa,
8773
- subscribeConfigChange as ab,
8774
- toToolCallView as ac,
8775
- toolRefusal as ad,
8776
- unregisterSegmentRenderer as ae,
8777
- uuid as af,
8778
- eventOrigin as ag,
8779
- activeElementIn as ah,
8780
- gateGlide as ai,
8781
- toolCallRenderer as aj,
8782
- toolRendererThatDrew as ak,
8783
- isMountedRenderer as al,
8784
- renderingVersionOf as am,
8785
- onRenderersChange as an,
8786
- mergeSegmentUpdate as ao,
8787
- writeStreamedMarkdown as ap,
8788
- stampSegmentActivity as aq,
8789
- stampSegmentOnInsert as ar,
8790
- stampSegmentOnUpdate as as,
8791
- renumberSegments as at,
8792
- openSegmentIds as au,
8793
- isTerminalStatus as av,
8794
- adoptMessageSegments as aw,
8795
- APARTE_DERIVED_SEGMENTS as ax,
8796
- rememberConversationStatus as ay,
8797
- tellConversationStatus as az,
9185
+ measureContext as a0,
9186
+ overrideSegmentRenderer as a1,
9187
+ parseAparteEventStream as a2,
9188
+ parseMarkdownToSegments as a3,
9189
+ populateBubbleFromMessage as a4,
9190
+ readableToAsyncIterable as a5,
9191
+ registerDefaultRenderers as a6,
9192
+ registerSegmentRenderer as a7,
9193
+ releaseDeferredHydration as a8,
9194
+ requestUserInput as a9,
9195
+ renumberSegments as aA,
9196
+ openSegmentIds as aB,
9197
+ isTerminalStatus as aC,
9198
+ adoptMessageSegments as aD,
9199
+ APARTE_DERIVED_SEGMENTS as aE,
9200
+ rememberConversationStatus as aF,
9201
+ tellConversationStatus as aG,
9202
+ conversationStatusOf as aH,
9203
+ usageTokens as aI,
9204
+ rootOf as aJ,
9205
+ resolveConfig as aa,
9206
+ revokeAttachmentUrls as ab,
9207
+ runWithConfig as ac,
9208
+ segmentDuration as ad,
9209
+ segmentTiming as ae,
9210
+ subscribeConfigChange as af,
9211
+ toToolCallView as ag,
9212
+ toolRefusal as ah,
9213
+ unregisterSegmentRenderer as ai,
9214
+ uuid as aj,
9215
+ eventOrigin as ak,
9216
+ activeElementIn as al,
9217
+ gateGlide as am,
9218
+ chatBoundaryOf as an,
9219
+ toolCallRenderer as ao,
9220
+ toolRendererThatDrew as ap,
9221
+ isMountedRenderer as aq,
9222
+ renderingVersionOf as ar,
9223
+ onRenderersChange as as,
9224
+ mergeSegmentUpdate as at,
9225
+ writeStreamedMarkdown as au,
9226
+ CheckpointStateWatcher as av,
9227
+ chatIdOf as aw,
9228
+ stampSegmentActivity as ax,
9229
+ stampSegmentOnInsert as ay,
9230
+ stampSegmentOnUpdate as az,
8798
9231
  APARTE_DEFAULT_BUBBLE_ACTIONS as b,
8799
9232
  APARTE_DEFAULT_HOST_HANDLERS as c,
8800
9233
  APARTE_DEFAULT_ICON_FALLBACKS as d,
@@ -8821,4 +9254,4 @@ export {
8821
9254
  collectRendererStyles as y,
8822
9255
  contentToText as z
8823
9256
  };
8824
- //# sourceMappingURL=defer-hydration-BwMqeRHq.js.map
9257
+ //# sourceMappingURL=defer-hydration-8vI8xNNx.js.map