@varo-ui/ai 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -10,13 +10,51 @@ function splitGraphemes(value) {
10
10
  function clamp(value, min, max) {
11
11
  return Math.min(max, Math.max(min, value));
12
12
  }
13
+ function hasUnclosedFence(value) {
14
+ let marker;
15
+ let markerLength = 0;
16
+ for (const line of value.split(/\r?\n/)) {
17
+ const match = /^ {0,3}(`{3,}|~{3,})/.exec(line);
18
+ if (!match) continue;
19
+ const nextMarker = match[1][0];
20
+ const nextLength = match[1].length;
21
+ if (!marker) {
22
+ marker = nextMarker;
23
+ markerLength = nextLength;
24
+ } else if (marker === nextMarker && nextLength >= markerLength) {
25
+ marker = void 0;
26
+ markerLength = 0;
27
+ }
28
+ }
29
+ return marker !== void 0;
30
+ }
31
+ function fenceSafeCommitCount(visible, source, segments) {
32
+ if (segments.length === 0) return 0;
33
+ const candidate = visible + segments.join("");
34
+ const lineStart = candidate.lastIndexOf("\n") + 1;
35
+ const candidateLine = candidate.slice(lineStart);
36
+ const sourceLineEnd = source.indexOf("\n", lineStart);
37
+ const sourceLine = source.slice(lineStart, sourceLineEnd < 0 ? source.length : sourceLineEnd);
38
+ const match = /^ {0,3}(`+|~+)/.exec(sourceLine);
39
+ if (!match) return segments.length;
40
+ const marker = match[1];
41
+ const unresolvedCandidate = marker.length < 3 && sourceLine.trim() === marker;
42
+ const completeFence = marker.length >= 3;
43
+ if ((unresolvedCandidate || completeFence) && /^ {0,3}(?:`+|~+)/.test(candidateLine)) return 0;
44
+ return segments.length;
45
+ }
13
46
  function createTimerTextStream(options = {}) {
14
47
  const minCharsPerSecond = Math.max(1, options.minCharsPerSecond ?? 40);
15
- const maxCharsPerSecond = Math.max(minCharsPerSecond, options.maxCharsPerSecond ?? 800);
16
- const targetLatencyMs = Math.max(50, options.targetLatencyMs ?? 700);
17
- const maxCommitFps = Math.max(1, Math.min(30, options.maxCommitFps ?? 20));
18
- const maxCharsPerCommit = Math.max(1, options.maxCharsPerCommit ?? 64);
19
- const catchUpThreshold = Math.max(1, options.catchUpThreshold ?? 320);
48
+ const maxCharsPerSecond = Math.max(minCharsPerSecond, options.maxCharsPerSecond ?? 1e3);
49
+ const targetLatencyMs = Math.max(1, options.targetLatencyMs ?? 900);
50
+ const catchUpLatencyMs = Math.max(1, options.catchUpLatencyMs ?? 350);
51
+ const maxCommitFps = Math.max(1, Math.min(30, options.maxCommitFps ?? 30));
52
+ const maxCharsPerCommit = Math.max(1, options.maxCharsPerCommit ?? 80);
53
+ const catchUpThreshold = Math.max(0, options.catchUpThreshold ?? 600);
54
+ const startDelayMs = Math.max(0, options.startDelayMs ?? 80);
55
+ const flushOnFinish = options.flushOnFinish ?? false;
56
+ const burstInitialContent = options.burstInitialContent ?? false;
57
+ const burstRevealThresholdChars = Math.max(1, options.burstRevealThresholdChars ?? 2048);
20
58
  const listeners = /* @__PURE__ */ new Set();
21
59
  const pending = [];
22
60
  let pendingOffset = 0;
@@ -27,16 +65,17 @@ function createTimerTextStream(options = {}) {
27
65
  let destroyed = false;
28
66
  let timer;
29
67
  let lastTick = Date.now();
68
+ let firstPendingAt = 0;
30
69
  let budget = 0;
31
70
  function pendingCount() {
32
71
  return pending.length - pendingOffset;
33
72
  }
34
73
  function snapshot() {
35
- const pendingChars = pendingCount();
74
+ const pendingChars = Math.max(0, source.length - visible.length);
36
75
  return {
37
- caughtUp: pendingChars === 0,
76
+ caughtUp: pendingCount() === 0,
38
77
  done,
39
- final: done && pendingChars === 0,
78
+ final: done && pendingCount() === 0,
40
79
  paused,
41
80
  pendingChars,
42
81
  source,
@@ -59,6 +98,11 @@ function createTimerTextStream(options = {}) {
59
98
  timer = void 0;
60
99
  if (destroyed || paused) return;
61
100
  const now = Date.now();
101
+ if (firstPendingAt && now - firstPendingAt < startDelayMs) {
102
+ schedule();
103
+ return;
104
+ }
105
+ firstPendingAt = 0;
62
106
  const deltaMs = Math.max(1, now - lastTick);
63
107
  lastTick = now;
64
108
  const backlog = pendingCount();
@@ -66,12 +110,27 @@ function createTimerTextStream(options = {}) {
66
110
  emit();
67
111
  return;
68
112
  }
69
- const cps = clamp(backlog * 1e3 / targetLatencyMs * (backlog >= catchUpThreshold ? 1.8 : 1), minCharsPerSecond, maxCharsPerSecond);
113
+ const latencyMs = backlog >= catchUpThreshold ? catchUpLatencyMs : targetLatencyMs;
114
+ const cps = clamp(backlog * 1e3 / latencyMs, minCharsPerSecond, maxCharsPerSecond);
70
115
  budget += cps * deltaMs / 1e3;
71
116
  const commitCount = Math.min(backlog, maxCharsPerCommit, Math.max(1, Math.floor(budget)));
72
- budget = Math.max(0, budget - commitCount);
73
- visible += pending.slice(pendingOffset, pendingOffset + commitCount).join("");
74
- pendingOffset += commitCount;
117
+ let proposedSegments = pending.slice(pendingOffset, pendingOffset + commitCount);
118
+ let safeCommitCount = fenceSafeCommitCount(visible, source, proposedSegments);
119
+ if (safeCommitCount === 0) {
120
+ const newlineOffset = pending.slice(pendingOffset).findIndex((segment) => segment.includes("\n"));
121
+ if (newlineOffset >= 0) {
122
+ proposedSegments = pending.slice(pendingOffset, pendingOffset + newlineOffset + 1);
123
+ safeCommitCount = fenceSafeCommitCount(visible, source, proposedSegments);
124
+ }
125
+ }
126
+ if (safeCommitCount === 0) {
127
+ emit();
128
+ schedule();
129
+ return;
130
+ }
131
+ budget = Math.max(0, budget - safeCommitCount);
132
+ visible += proposedSegments.slice(0, safeCommitCount).join("");
133
+ pendingOffset += safeCommitCount;
75
134
  compactPending();
76
135
  emit();
77
136
  schedule();
@@ -80,27 +139,37 @@ function createTimerTextStream(options = {}) {
80
139
  clearTimeout(timer);
81
140
  timer = void 0;
82
141
  }
142
+ function destroy() {
143
+ if (destroyed) return;
144
+ destroyed = true;
145
+ cancelTimer();
146
+ pending.length = 0;
147
+ listeners.clear();
148
+ }
83
149
  return {
84
- destroy() {
85
- if (destroyed) return;
86
- destroyed = true;
87
- cancelTimer();
88
- pending.length = 0;
89
- listeners.clear();
90
- },
150
+ destroy,
151
+ dispose: destroy,
91
152
  enqueue(chunk) {
92
153
  if (destroyed || !chunk) return;
93
154
  if (done) done = false;
155
+ const initialChunk = source.length === 0;
94
156
  source += chunk;
95
157
  pending.push(...splitGraphemes(chunk));
158
+ if (initialChunk) firstPendingAt = Date.now();
96
159
  lastTick = Date.now();
160
+ if (initialChunk && burstInitialContent && pendingCount() >= burstRevealThresholdChars && !hasUnclosedFence(source)) {
161
+ visible = source;
162
+ pending.length = 0;
163
+ pendingOffset = 0;
164
+ firstPendingAt = 0;
165
+ }
97
166
  emit();
98
167
  schedule();
99
168
  },
100
169
  finish(finishOptions = {}) {
101
170
  if (destroyed) return;
102
171
  done = true;
103
- if (finishOptions.flush) {
172
+ if (finishOptions.flush ?? flushOnFinish) {
104
173
  visible = source;
105
174
  pending.length = 0;
106
175
  pendingOffset = 0;
@@ -125,7 +194,7 @@ function createTimerTextStream(options = {}) {
125
194
  cancelTimer();
126
195
  emit();
127
196
  },
128
- reset(content = "") {
197
+ reset(content = "", _resetOptions = {}) {
129
198
  cancelTimer();
130
199
  source = content;
131
200
  visible = content;
@@ -134,6 +203,7 @@ function createTimerTextStream(options = {}) {
134
203
  done = false;
135
204
  paused = false;
136
205
  budget = 0;
206
+ firstPendingAt = 0;
137
207
  lastTick = Date.now();
138
208
  emit();
139
209
  },
@@ -156,6 +226,7 @@ function createTextStream(options = {}) {
156
226
  const controller = createSmoothMarkdownStream(options);
157
227
  return {
158
228
  destroy: controller.destroy,
229
+ dispose: controller.dispose,
159
230
  enqueue: controller.enqueue,
160
231
  finish: controller.finish,
161
232
  flush: controller.flush,
@@ -168,6 +239,12 @@ function createTextStream(options = {}) {
168
239
  }
169
240
  //#endregion
170
241
  //#region src/agent-stream.ts
242
+ function requestIteratorCleanup(iterator) {
243
+ try {
244
+ const cleanup = iterator.return?.();
245
+ if (cleanup) cleanup.catch(() => void 0);
246
+ } catch {}
247
+ }
171
248
  function initialSnapshot() {
172
249
  return {
173
250
  data: [],
@@ -395,6 +472,7 @@ function createAgentStreamController(options = {}) {
395
472
  resolveCancellation = resolve;
396
473
  });
397
474
  cancelActiveConnect = resolveCancellation;
475
+ let protocolTerminal = false;
398
476
  try {
399
477
  try {
400
478
  while (!cancelled && !destroyed) {
@@ -403,7 +481,12 @@ function createAgentStreamController(options = {}) {
403
481
  result
404
482
  })), cancellation.then(() => ({ kind: "cancelled" }))]);
405
483
  if (outcome.kind === "cancelled" || outcome.result.done) break;
406
- push(outcome.result.value);
484
+ const event = outcome.result.value;
485
+ push(event);
486
+ if (event.type === "done" || event.type === "error") {
487
+ protocolTerminal = true;
488
+ break;
489
+ }
407
490
  }
408
491
  if (!transportDone && !cancelled && !destroyed) push({ type: "done" });
409
492
  } catch (error) {
@@ -413,7 +496,7 @@ function createAgentStreamController(options = {}) {
413
496
  type: "error"
414
497
  });
415
498
  } finally {
416
- if (cancelled || destroyed) iterator.return?.();
499
+ if (protocolTerminal || cancelled || destroyed) requestIteratorCleanup(iterator);
417
500
  }
418
501
  return await waitForTerminal();
419
502
  } finally {
@@ -444,20 +527,41 @@ function createAgentEventChannel() {
444
527
  let ended = false;
445
528
  let failure;
446
529
  let wake;
530
+ function stopConsuming() {
531
+ ended = true;
532
+ queue.length = 0;
533
+ notify();
534
+ }
447
535
  async function* iterate() {
448
- while (!ended || queue.length > 0) {
449
- if (failure) throw failure;
450
- const event = queue.shift();
451
- if (event) {
452
- yield event;
453
- continue;
536
+ try {
537
+ while (!ended || queue.length > 0) {
538
+ if (failure) throw failure;
539
+ const event = queue.shift();
540
+ if (event) {
541
+ yield event;
542
+ continue;
543
+ }
544
+ await new Promise((resolve) => {
545
+ wake = resolve;
546
+ });
547
+ wake = void 0;
454
548
  }
455
- await new Promise((resolve) => {
456
- wake = resolve;
457
- });
458
- wake = void 0;
549
+ if (failure) throw failure;
550
+ } finally {
551
+ stopConsuming();
459
552
  }
460
- if (failure) throw failure;
553
+ }
554
+ function createIterator() {
555
+ const iterator = iterate();
556
+ return {
557
+ next() {
558
+ return iterator.next();
559
+ },
560
+ return() {
561
+ stopConsuming();
562
+ return iterator.return(void 0);
563
+ }
564
+ };
461
565
  }
462
566
  function notify() {
463
567
  wake?.();
@@ -477,7 +581,7 @@ function createAgentEventChannel() {
477
581
  queue.push(event);
478
582
  notify();
479
583
  },
480
- source: { [Symbol.asyncIterator]: iterate }
584
+ source: { [Symbol.asyncIterator]: createIterator }
481
585
  };
482
586
  }
483
587
  //#endregion
@@ -656,6 +760,68 @@ function createStreamingMarkdownParser(options = {}) {
656
760
  }
657
761
  //#endregion
658
762
  //#region src/markdown-view.ts
763
+ function richTextChildren(children) {
764
+ const result = [];
765
+ for (const child of children ?? []) {
766
+ const normalized = richTextNode(child);
767
+ if (!normalized) return;
768
+ result.push(normalized);
769
+ }
770
+ return result;
771
+ }
772
+ function richTextNode(node) {
773
+ if (node.kind === "text") return {
774
+ text: node.text ?? "",
775
+ type: "text"
776
+ };
777
+ if (node.kind === "hardbreak") return { name: "br" };
778
+ if (node.kind === "checkbox") return {
779
+ text: node.checked ? "☑ " : "☐ ",
780
+ type: "text"
781
+ };
782
+ const name = {
783
+ emphasis: "em",
784
+ strikethrough: "del",
785
+ strong: "strong",
786
+ subscript: "sub",
787
+ superscript: "sup"
788
+ }[node.kind];
789
+ if (name) {
790
+ const children = richTextChildren(node.children);
791
+ return children ? {
792
+ children,
793
+ name
794
+ } : void 0;
795
+ }
796
+ if (node.kind === "highlight" || node.kind === "insert") {
797
+ const children = richTextChildren(node.children);
798
+ if (!children) return;
799
+ return {
800
+ attrs: { style: node.kind === "highlight" ? "background:rgba(250,146,0,.18);border-radius:2px;" : "text-decoration:underline;" },
801
+ children,
802
+ name: "span"
803
+ };
804
+ }
805
+ if (node.kind === "inline-code") return {
806
+ attrs: { style: "font-family:monospace;padding:1px 4px;border-radius:4px;background:rgba(127,127,127,.12);" },
807
+ children: [{
808
+ text: node.text ?? "",
809
+ type: "text"
810
+ }],
811
+ name: "code"
812
+ };
813
+ if (node.kind === "math-inline") return {
814
+ attrs: { style: "font-family:monospace;" },
815
+ children: [{
816
+ text: node.text ?? "",
817
+ type: "text"
818
+ }],
819
+ name: "span"
820
+ };
821
+ }
822
+ function toAgentRichTextNodes(nodes) {
823
+ return richTextChildren(nodes);
824
+ }
659
825
  function stringValue(value, fallback = "") {
660
826
  return typeof value === "string" ? value : fallback;
661
827
  }
@@ -672,7 +838,7 @@ function nodeField(node, key) {
672
838
  return key in node ? Reflect.get(node, key) : void 0;
673
839
  }
674
840
  function normalizeUnknownNode(value) {
675
- if (!isUnknownRecord(value) || typeof value.type !== "string" || typeof value.raw !== "string") return void 0;
841
+ if (!isUnknownRecord(value) || typeof value.type !== "string" || typeof value.raw !== "string") return;
676
842
  return normalizeMarkdownNode(value);
677
843
  }
678
844
  function normalizeChildren(value) {
@@ -917,19 +1083,40 @@ function createAgentSseEventSource() {
917
1083
  const dataLines = [];
918
1084
  let textBuffer = "";
919
1085
  let ended = false;
1086
+ function clearBufferedInput() {
1087
+ dataLines.length = 0;
1088
+ textBuffer = "";
1089
+ }
1090
+ function closeSource() {
1091
+ if (ended) return;
1092
+ ended = true;
1093
+ clearBufferedInput();
1094
+ channel.end();
1095
+ }
1096
+ function failSource(error) {
1097
+ if (ended) return;
1098
+ ended = true;
1099
+ clearBufferedInput();
1100
+ channel.fail(error);
1101
+ }
1102
+ function finishWith(event) {
1103
+ channel.push(event);
1104
+ closeSource();
1105
+ }
920
1106
  function dispatch() {
921
1107
  if (dataLines.length === 0 || ended) return;
922
1108
  const payload = dataLines.join("\n");
923
1109
  dataLines.length = 0;
924
1110
  if (payload === "[DONE]") {
925
- channel.push({ type: "done" });
1111
+ finishWith({ type: "done" });
926
1112
  return;
927
1113
  }
928
1114
  try {
929
- channel.push(parseAgentStreamEvent(JSON.parse(payload)));
1115
+ const event = parseAgentStreamEvent(JSON.parse(payload));
1116
+ if (event.type === "done" || event.type === "error") finishWith(event);
1117
+ else channel.push(event);
930
1118
  } catch (error) {
931
- ended = true;
932
- channel.fail(error);
1119
+ failSource(error);
933
1120
  }
934
1121
  }
935
1122
  function processLine(line) {
@@ -949,10 +1136,12 @@ function createAgentSseEventSource() {
949
1136
  }
950
1137
  }
951
1138
  function appendText(value) {
1139
+ if (ended) return;
952
1140
  textBuffer += value;
953
1141
  let lineEnd = textBuffer.indexOf("\n");
954
1142
  while (lineEnd >= 0) {
955
1143
  processLine(textBuffer.slice(0, lineEnd));
1144
+ if (ended) return;
956
1145
  textBuffer = textBuffer.slice(lineEnd + 1);
957
1146
  lineEnd = textBuffer.indexOf("\n");
958
1147
  }
@@ -961,15 +1150,13 @@ function createAgentSseEventSource() {
961
1150
  end() {
962
1151
  if (ended) return;
963
1152
  appendText(decoder.decode(/* @__PURE__ */ new Uint8Array(0), true));
1153
+ if (ended) return;
964
1154
  if (textBuffer.length > 0) processLine(textBuffer);
965
1155
  dispatch();
966
- ended = true;
967
- channel.end();
1156
+ closeSource();
968
1157
  },
969
1158
  fail(error) {
970
- if (ended) return;
971
- ended = true;
972
- channel.fail(error);
1159
+ failSource(error);
973
1160
  },
974
1161
  feed(chunk) {
975
1162
  if (ended) return;
@@ -979,6 +1166,113 @@ function createAgentSseEventSource() {
979
1166
  };
980
1167
  }
981
1168
  //#endregion
982
- export { createAgentEventChannel, createAgentSseEventSource, createAgentStreamController, createStreamingMarkdownParser, createTextStream, normalizeMarkdownNode, normalizeMarkdownNodes, parseAgentStreamEvent };
1169
+ //#region src/thread.ts
1170
+ const DESTROYED_ERROR = "Agent thread controller is destroyed";
1171
+ function assertVersionId(id) {
1172
+ if (id.length === 0) throw new Error("Agent thread version id must not be empty");
1173
+ }
1174
+ function freezeVersion(version) {
1175
+ return Object.freeze({ ...version });
1176
+ }
1177
+ function freezeSnapshot(versions, activeVersionId) {
1178
+ if (activeVersionId === void 0) return Object.freeze({ versions });
1179
+ return Object.freeze({
1180
+ activeVersionId,
1181
+ versions
1182
+ });
1183
+ }
1184
+ function validateSnapshot(initial) {
1185
+ const sourceVersions = initial?.versions ?? [];
1186
+ const versionsById = /* @__PURE__ */ new Map();
1187
+ for (const version of sourceVersions) {
1188
+ assertVersionId(version.id);
1189
+ if (versionsById.has(version.id)) throw new Error(`Agent thread version "${version.id}" is duplicated`);
1190
+ versionsById.set(version.id, version);
1191
+ }
1192
+ for (const version of sourceVersions) if (version.parentId !== void 0 && !versionsById.has(version.parentId)) throw new Error(`Agent thread parent version "${version.parentId}" does not exist`);
1193
+ const resolvedIds = /* @__PURE__ */ new Set();
1194
+ for (const version of sourceVersions) {
1195
+ if (resolvedIds.has(version.id)) continue;
1196
+ const path = [];
1197
+ const pathIds = /* @__PURE__ */ new Set();
1198
+ let current = version;
1199
+ while (current !== void 0 && !resolvedIds.has(current.id)) {
1200
+ if (pathIds.has(current.id)) throw new Error(`Agent thread version "${current.id}" has a cyclic parent chain`);
1201
+ path.push(current.id);
1202
+ pathIds.add(current.id);
1203
+ current = current.parentId === void 0 ? void 0 : versionsById.get(current.parentId);
1204
+ }
1205
+ path.forEach((id) => resolvedIds.add(id));
1206
+ }
1207
+ const activeVersionId = initial?.activeVersionId;
1208
+ if (activeVersionId !== void 0 && !versionsById.has(activeVersionId)) throw new Error(`Agent thread active version "${activeVersionId}" does not exist`);
1209
+ return freezeSnapshot(Object.freeze(sourceVersions.map(freezeVersion)), activeVersionId);
1210
+ }
1211
+ function createAgentThreadController(initial) {
1212
+ const listeners = /* @__PURE__ */ new Set();
1213
+ let snapshot = validateSnapshot(initial);
1214
+ let destroyed = false;
1215
+ function assertActive() {
1216
+ if (destroyed) throw new Error(DESTROYED_ERROR);
1217
+ }
1218
+ function findVersion(id) {
1219
+ return snapshot.versions.find((version) => version.id === id);
1220
+ }
1221
+ function replace(nextSnapshot) {
1222
+ snapshot = nextSnapshot;
1223
+ listeners.forEach((listener) => listener());
1224
+ }
1225
+ function append(version) {
1226
+ assertActive();
1227
+ assertVersionId(version.id);
1228
+ if (findVersion(version.id)) throw new Error(`Agent thread version "${version.id}" already exists`);
1229
+ if (version.parentId !== void 0 && !findVersion(version.parentId)) throw new Error(`Agent thread parent version "${version.parentId}" does not exist`);
1230
+ replace(freezeSnapshot(Object.freeze([...snapshot.versions, freezeVersion(version)]), version.id));
1231
+ }
1232
+ function fork(parentId, version) {
1233
+ assertActive();
1234
+ assertVersionId(version.id);
1235
+ if (findVersion(version.id)) throw new Error(`Agent thread version "${version.id}" already exists`);
1236
+ if (!findVersion(parentId)) throw new Error(`Agent thread parent version "${parentId}" does not exist`);
1237
+ const forkedVersion = freezeVersion({
1238
+ ...version,
1239
+ parentId
1240
+ });
1241
+ replace(freezeSnapshot(Object.freeze([...snapshot.versions, forkedVersion]), forkedVersion.id));
1242
+ }
1243
+ function select(id) {
1244
+ assertActive();
1245
+ if (!findVersion(id)) throw new Error(`Agent thread version "${id}" does not exist`);
1246
+ replace(freezeSnapshot(snapshot.versions, id));
1247
+ }
1248
+ function reset(nextInitial) {
1249
+ assertActive();
1250
+ replace(validateSnapshot(nextInitial));
1251
+ }
1252
+ function subscribe(listener) {
1253
+ assertActive();
1254
+ listeners.add(listener);
1255
+ return () => {
1256
+ listeners.delete(listener);
1257
+ };
1258
+ }
1259
+ return {
1260
+ append,
1261
+ destroy() {
1262
+ if (destroyed) return;
1263
+ destroyed = true;
1264
+ listeners.clear();
1265
+ },
1266
+ fork,
1267
+ getSnapshot() {
1268
+ return snapshot;
1269
+ },
1270
+ reset,
1271
+ select,
1272
+ subscribe
1273
+ };
1274
+ }
1275
+ //#endregion
1276
+ export { createAgentEventChannel, createAgentSseEventSource, createAgentStreamController, createAgentThreadController, createStreamingMarkdownParser, createTextStream, normalizeMarkdownNode, normalizeMarkdownNodes, parseAgentStreamEvent, toAgentRichTextNodes };
983
1277
 
984
1278
  //# sourceMappingURL=index.mjs.map