@adminforth/agent 1.44.0 → 1.44.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.
package/agentEvents.ts CHANGED
@@ -17,6 +17,11 @@ export type AgentEvent =
17
17
  type: "tool-call";
18
18
  data: ToolCallEvent;
19
19
  }
20
+ | {
21
+ type: "rendering";
22
+ phase: "start" | "end";
23
+ label: string;
24
+ }
20
25
  | {
21
26
  type: "transcript";
22
27
  text: string;
package/build.log CHANGED
@@ -62,5 +62,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
62
62
  custom/speech_recognition_frontend/types/
63
63
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
64
64
 
65
- sent 1,666,161 bytes received 921 bytes 3,334,164.00 bytes/sec
66
- total size is 1,662,008 speedup is 1.00
65
+ sent 1,667,675 bytes received 921 bytes 3,337,192.00 bytes/sec
66
+ total size is 1,663,522 speedup is 1.00
@@ -106,7 +106,13 @@ onMounted(async () => {
106
106
  if( coreStore.isMobile ) {
107
107
  agentStore.setIsTeleportedToBody(false);
108
108
  } else {
109
- agentStore.setIsTeleportedToBody(isTeleportedToBodyFromLocalStorage || props.meta.stickByDefault);
109
+ const shouldTeleportToBody = isTeleportedToBodyFromLocalStorage || props.meta.stickByDefault;
110
+ const savedIsChatOpen = agentStore.getLocalStorageItem('isChatOpen');
111
+
112
+ agentStore.setIsTeleportedToBody(shouldTeleportToBody);
113
+ if (shouldTeleportToBody && savedIsChatOpen === null) {
114
+ agentStore.setIsChatOpen(true);
115
+ }
110
116
  }
111
117
  await agentStore.fetchSessionsList();
112
118
  });
@@ -174,7 +174,10 @@ export const useAgentStore = defineStore('agent', () => {
174
174
  if (!coreStore.isMobile) {
175
175
  const savedIsTeleportedToBody = getLocalStorageItem('isTeleportedToBody');
176
176
  const savedIsTeleportedToBodyBeforeFullScreen = getLocalStorageItem('isTeleportedToBodyBeforeFullScreen');
177
- const isTeleportedToBodyFromLocalStorage = savedIsTeleportedToBody === 'true' || savedIsTeleportedToBodyBeforeFullScreen === 'true';
177
+ let isTeleportedToBodyFromLocalStorage = true;
178
+ if (savedIsTeleportedToBody !== null || savedIsTeleportedToBodyBeforeFullScreen !== null) {
179
+ isTeleportedToBodyFromLocalStorage = savedIsTeleportedToBody === 'true' || savedIsTeleportedToBodyBeforeFullScreen === 'true';
180
+ }
178
181
  const savedIsChatOpen = getLocalStorageItem('isChatOpen');
179
182
 
180
183
  setIsTeleportedToBody(isTeleportedToBodyFromLocalStorage);
@@ -374,4 +377,4 @@ export const useAgentStore = defineStore('agent', () => {
374
377
  setCurrentChatStatus,
375
378
  updateLastAgentMessage
376
379
  }
377
- })
380
+ })
@@ -39,6 +39,15 @@
39
39
  <template v-for="(part, index) in ToolOrReasoningParts" :key="index">
40
40
  <ReasoningRenderer v-if="part.type === 'reasoning'" :state="part.state" :text="part.text" />
41
41
  <ToolsGroup v-else-if="part.type==='data-tool-call'" :toolGroup="groupToolCallParts(message, part)" />
42
+ <li v-else-if="part.type === 'data-rendering'" class="mb-6 mx-2 mt-2 px-2 z-50 overflow-hidden">
43
+ <span class="bg-lightNavbar dark:bg-darkNavbar absolute flex items-center text-listTableHeadingText dark:text-darkListTableHeadingText justify-center w-5 h-5 rounded-full -start-[0.68rem] ring-4 ring-lightNavbar dark:ring-darkNavbar">
44
+ <div class="w-2 h-2 rounded-full bg-current animate-pulse"></div>
45
+ </span>
46
+ <h3 class="flex items-center mb-1 text-sm ml-3 gap-1 text-listTableHeadingText dark:text-darkListTableHeadingText">
47
+ <span class="font-semibold">{{ part.data?.label ?? 'Rendering...' }}</span>
48
+ <ThreeDotsAnimation />
49
+ </h3>
50
+ </li>
42
51
  </template>
43
52
  </ol>
44
53
  </CustomAutoScrollContainer>
@@ -73,7 +82,11 @@
73
82
  const isExpanded = ref(true);
74
83
  let isUserScrolled = false;
75
84
  const ToolOrReasoningParts = computed(() => {
76
- return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
85
+ return props.message.parts.filter((part: IPart) => {
86
+ return part.type === 'data-tool-call'
87
+ || part.type === 'reasoning'
88
+ || isActiveRenderingPart(part);
89
+ });
77
90
  });
78
91
  const isResponseInProgress = computed(() =>{
79
92
  return props.isLastMessageInChat && agentStore.isResponseInProgress;
@@ -157,6 +170,14 @@
157
170
  });
158
171
  };
159
172
 
173
+ function isActiveRenderingPart(part: IPart) {
174
+ return part.type === 'data-rendering'
175
+ && part.data?.phase === 'start'
176
+ && !props.message.parts.some((candidate: IPart) => {
177
+ return candidate.type === 'data-rendering' && candidate.data?.phase === 'end';
178
+ });
179
+ }
180
+
160
181
  const groupToolCallParts = (message: IMessage, currentPart: IPart): IToolGroup[] => {
161
182
  if (currentPart.type !== 'data-tool-call') {
162
183
  return [];
@@ -259,4 +280,4 @@
259
280
  }
260
281
  }
261
282
 
262
- </style>
283
+ </style>
@@ -72,7 +72,7 @@ let highlightModulePromise: Promise<typeof import('./incremarkCodeHighlight')> |
72
72
  const sourceCode = computed(() => props.node.value ?? '');
73
73
  const language = computed(() => props.node.lang?.trim().toLowerCase() || 'text');
74
74
  const languageLabel = computed(() => language.value === 'vega-lite' ? '' : props.node.lang?.trim() || 'text');
75
- const shouldRenderVega = computed(() => language.value === 'vega-lite' && props.blockStatus === 'completed');
75
+ const shouldRenderVega = computed(() => language.value === 'vega-lite');
76
76
  const codeTheme = computed<IncremarkCodeTheme>(() => {
77
77
  const requestedTheme = props.theme ?? (prefersDarkMode.value ? props.darkTheme : props.lightTheme);
78
78
 
@@ -390,4 +390,4 @@ function clearVega() {
390
390
  :deep(.incremark-vega){
391
391
  padding: 0;
392
392
  }
393
- </style>
393
+ </style>
package/custom/types.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  export interface IPartData {
2
- toolCallId: string;
3
- toolName: string;
4
- phase: 'start' | 'end';
2
+ toolCallId?: string;
3
+ toolName?: string;
4
+ phase?: 'start' | 'end';
5
+ label?: string;
5
6
  input?: any;
6
7
  output?: any;
7
8
  durationMs?: number;
8
9
  toolInfo?: string;
9
10
  }
10
11
  export interface IPart {
11
- type: 'reasoning' | 'data-tool-call' | 'text';
12
+ type: 'reasoning' | 'data-tool-call' | 'data-rendering' | 'text';
12
13
  text?: string;
13
14
  state?: 'started' | 'thinking' | 'processing' | 'streaming' | 'done';
14
15
  data?: IPartData;
@@ -11,6 +11,10 @@ export type AgentEvent = {
11
11
  } | {
12
12
  type: "tool-call";
13
13
  data: ToolCallEvent;
14
+ } | {
15
+ type: "rendering";
16
+ phase: "start" | "end";
17
+ label: string;
14
18
  } | {
15
19
  type: "transcript";
16
20
  text: string;
@@ -106,7 +106,13 @@ onMounted(async () => {
106
106
  if( coreStore.isMobile ) {
107
107
  agentStore.setIsTeleportedToBody(false);
108
108
  } else {
109
- agentStore.setIsTeleportedToBody(isTeleportedToBodyFromLocalStorage || props.meta.stickByDefault);
109
+ const shouldTeleportToBody = isTeleportedToBodyFromLocalStorage || props.meta.stickByDefault;
110
+ const savedIsChatOpen = agentStore.getLocalStorageItem('isChatOpen');
111
+
112
+ agentStore.setIsTeleportedToBody(shouldTeleportToBody);
113
+ if (shouldTeleportToBody && savedIsChatOpen === null) {
114
+ agentStore.setIsChatOpen(true);
115
+ }
110
116
  }
111
117
  await agentStore.fetchSessionsList();
112
118
  });
@@ -174,7 +174,10 @@ export const useAgentStore = defineStore('agent', () => {
174
174
  if (!coreStore.isMobile) {
175
175
  const savedIsTeleportedToBody = getLocalStorageItem('isTeleportedToBody');
176
176
  const savedIsTeleportedToBodyBeforeFullScreen = getLocalStorageItem('isTeleportedToBodyBeforeFullScreen');
177
- const isTeleportedToBodyFromLocalStorage = savedIsTeleportedToBody === 'true' || savedIsTeleportedToBodyBeforeFullScreen === 'true';
177
+ let isTeleportedToBodyFromLocalStorage = true;
178
+ if (savedIsTeleportedToBody !== null || savedIsTeleportedToBodyBeforeFullScreen !== null) {
179
+ isTeleportedToBodyFromLocalStorage = savedIsTeleportedToBody === 'true' || savedIsTeleportedToBodyBeforeFullScreen === 'true';
180
+ }
178
181
  const savedIsChatOpen = getLocalStorageItem('isChatOpen');
179
182
 
180
183
  setIsTeleportedToBody(isTeleportedToBodyFromLocalStorage);
@@ -374,4 +377,4 @@ export const useAgentStore = defineStore('agent', () => {
374
377
  setCurrentChatStatus,
375
378
  updateLastAgentMessage
376
379
  }
377
- })
380
+ })
@@ -39,6 +39,15 @@
39
39
  <template v-for="(part, index) in ToolOrReasoningParts" :key="index">
40
40
  <ReasoningRenderer v-if="part.type === 'reasoning'" :state="part.state" :text="part.text" />
41
41
  <ToolsGroup v-else-if="part.type==='data-tool-call'" :toolGroup="groupToolCallParts(message, part)" />
42
+ <li v-else-if="part.type === 'data-rendering'" class="mb-6 mx-2 mt-2 px-2 z-50 overflow-hidden">
43
+ <span class="bg-lightNavbar dark:bg-darkNavbar absolute flex items-center text-listTableHeadingText dark:text-darkListTableHeadingText justify-center w-5 h-5 rounded-full -start-[0.68rem] ring-4 ring-lightNavbar dark:ring-darkNavbar">
44
+ <div class="w-2 h-2 rounded-full bg-current animate-pulse"></div>
45
+ </span>
46
+ <h3 class="flex items-center mb-1 text-sm ml-3 gap-1 text-listTableHeadingText dark:text-darkListTableHeadingText">
47
+ <span class="font-semibold">{{ part.data?.label ?? 'Rendering...' }}</span>
48
+ <ThreeDotsAnimation />
49
+ </h3>
50
+ </li>
42
51
  </template>
43
52
  </ol>
44
53
  </CustomAutoScrollContainer>
@@ -73,7 +82,11 @@
73
82
  const isExpanded = ref(true);
74
83
  let isUserScrolled = false;
75
84
  const ToolOrReasoningParts = computed(() => {
76
- return props.message.parts.filter((part: IPart) => part.type === 'data-tool-call' || part.type === 'reasoning');
85
+ return props.message.parts.filter((part: IPart) => {
86
+ return part.type === 'data-tool-call'
87
+ || part.type === 'reasoning'
88
+ || isActiveRenderingPart(part);
89
+ });
77
90
  });
78
91
  const isResponseInProgress = computed(() =>{
79
92
  return props.isLastMessageInChat && agentStore.isResponseInProgress;
@@ -157,6 +170,14 @@
157
170
  });
158
171
  };
159
172
 
173
+ function isActiveRenderingPart(part: IPart) {
174
+ return part.type === 'data-rendering'
175
+ && part.data?.phase === 'start'
176
+ && !props.message.parts.some((candidate: IPart) => {
177
+ return candidate.type === 'data-rendering' && candidate.data?.phase === 'end';
178
+ });
179
+ }
180
+
160
181
  const groupToolCallParts = (message: IMessage, currentPart: IPart): IToolGroup[] => {
161
182
  if (currentPart.type !== 'data-tool-call') {
162
183
  return [];
@@ -259,4 +280,4 @@
259
280
  }
260
281
  }
261
282
 
262
- </style>
283
+ </style>
@@ -72,7 +72,7 @@ let highlightModulePromise: Promise<typeof import('./incremarkCodeHighlight')> |
72
72
  const sourceCode = computed(() => props.node.value ?? '');
73
73
  const language = computed(() => props.node.lang?.trim().toLowerCase() || 'text');
74
74
  const languageLabel = computed(() => language.value === 'vega-lite' ? '' : props.node.lang?.trim() || 'text');
75
- const shouldRenderVega = computed(() => language.value === 'vega-lite' && props.blockStatus === 'completed');
75
+ const shouldRenderVega = computed(() => language.value === 'vega-lite');
76
76
  const codeTheme = computed<IncremarkCodeTheme>(() => {
77
77
  const requestedTheme = props.theme ?? (prefersDarkMode.value ? props.darkTheme : props.lightTheme);
78
78
 
@@ -390,4 +390,4 @@ function clearVega() {
390
390
  :deep(.incremark-vega){
391
391
  padding: 0;
392
392
  }
393
- </style>
393
+ </style>
@@ -1,14 +1,15 @@
1
1
  export interface IPartData {
2
- toolCallId: string;
3
- toolName: string;
4
- phase: 'start' | 'end';
2
+ toolCallId?: string;
3
+ toolName?: string;
4
+ phase?: 'start' | 'end';
5
+ label?: string;
5
6
  input?: any;
6
7
  output?: any;
7
8
  durationMs?: number;
8
9
  toolInfo?: string;
9
10
  }
10
11
  export interface IPart {
11
- type: 'reasoning' | 'data-tool-call' | 'text';
12
+ type: 'reasoning' | 'data-tool-call' | 'data-rendering' | 'text';
12
13
  text?: string;
13
14
  state?: 'started' | 'thinking' | 'processing' | 'streaming' | 'done';
14
15
  data?: IPartData;
package/dist/index.js CHANGED
@@ -48,6 +48,8 @@ const sessionIdBodySchema = z.object({
48
48
  const createSessionBodySchema = z.object({
49
49
  triggerMessage: z.string().optional(),
50
50
  }).strict();
51
+ const VEGA_LITE_FENCE_START = "```vega-lite";
52
+ const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
51
53
  function isAbortError(error) {
52
54
  return (error instanceof DOMException && error.name === "AbortError") || (typeof error === "object" &&
53
55
  error !== null &&
@@ -203,8 +205,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
203
205
  runAgentTurn(input) {
204
206
  return __awaiter(this, void 0, void 0, function* () {
205
207
  var _a, e_1, _b, _c;
206
- var _d, _e, _f, _g, _h, _j;
208
+ var _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
207
209
  let fullResponse = "";
210
+ let bufferedTextDelta = "";
211
+ let isRenderingVegaLite = false;
208
212
  const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d : 1000;
209
213
  const selectedMode = (_e = this.options.modes.find((mode) => mode.name === input.modeName)) !== null && _e !== void 0 ? _e : this.options.modes[0];
210
214
  const [primaryModelSpec, summaryModelSpec] = yield Promise.all([
@@ -268,9 +272,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
268
272
  sequenceDebugSink: input.sequenceDebugCollector,
269
273
  });
270
274
  try {
271
- for (var _k = true, _l = __asyncValues(stream), _m; _m = yield _l.next(), _a = _m.done, !_a; _k = true) {
272
- _c = _m.value;
273
- _k = false;
275
+ for (var _p = true, _q = __asyncValues(stream), _r; _r = yield _q.next(), _a = _r.done, !_a; _p = true) {
276
+ _c = _r.value;
277
+ _p = false;
274
278
  const rawChunk = _c;
275
279
  if ((_g = input.abortSignal) === null || _g === void 0 ? void 0 : _g.aborted) {
276
280
  throw new DOMException("This operation was aborted", "AbortError");
@@ -303,20 +307,61 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
303
307
  }
304
308
  if (textDelta) {
305
309
  fullResponse += textDelta;
306
- yield ((_j = input.emit) === null || _j === void 0 ? void 0 : _j.call(input, {
310
+ bufferedTextDelta += textDelta;
311
+ if (bufferedTextDelta.includes(VEGA_LITE_FENCE_START) &&
312
+ !COMPLETE_VEGA_LITE_BLOCK_RE.test(bufferedTextDelta)) {
313
+ if (!isRenderingVegaLite) {
314
+ isRenderingVegaLite = true;
315
+ yield ((_j = input.emit) === null || _j === void 0 ? void 0 : _j.call(input, {
316
+ type: "rendering",
317
+ phase: "start",
318
+ label: "Rendering...",
319
+ }));
320
+ }
321
+ continue;
322
+ }
323
+ if (isRenderingVegaLite) {
324
+ isRenderingVegaLite = false;
325
+ yield ((_k = input.emit) === null || _k === void 0 ? void 0 : _k.call(input, {
326
+ type: "rendering",
327
+ phase: "end",
328
+ label: "Rendering...",
329
+ }));
330
+ }
331
+ const streamableLength = bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
332
+ ? bufferedTextDelta.length
333
+ : bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(bufferedTextDelta);
334
+ if (!streamableLength) {
335
+ continue;
336
+ }
337
+ yield ((_l = input.emit) === null || _l === void 0 ? void 0 : _l.call(input, {
307
338
  type: "text-delta",
308
- delta: textDelta,
339
+ delta: bufferedTextDelta.slice(0, streamableLength),
309
340
  }));
341
+ bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
310
342
  }
311
343
  }
312
344
  }
313
345
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
314
346
  finally {
315
347
  try {
316
- if (!_k && !_a && (_b = _l.return)) yield _b.call(_l);
348
+ if (!_p && !_a && (_b = _q.return)) yield _b.call(_q);
317
349
  }
318
350
  finally { if (e_1) throw e_1.error; }
319
351
  }
352
+ if (isRenderingVegaLite) {
353
+ yield ((_m = input.emit) === null || _m === void 0 ? void 0 : _m.call(input, {
354
+ type: "rendering",
355
+ phase: "end",
356
+ label: "Rendering...",
357
+ }));
358
+ }
359
+ if (bufferedTextDelta) {
360
+ yield ((_o = input.emit) === null || _o === void 0 ? void 0 : _o.call(input, {
361
+ type: "text-delta",
362
+ delta: bufferedTextDelta,
363
+ }));
364
+ }
320
365
  return {
321
366
  text: fullResponse,
322
367
  };
@@ -853,3 +898,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
853
898
  });
854
899
  }
855
900
  }
901
+ function getPartialVegaLiteFenceStartLength(text) {
902
+ for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
903
+ if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
904
+ return length;
905
+ }
906
+ }
907
+ return 0;
908
+ }
@@ -75,6 +75,18 @@ function createAgentEventStream(res, options = {}) {
75
75
  data: event,
76
76
  });
77
77
  },
78
+ rendering(phase, label) {
79
+ if (phase === "start") {
80
+ stream.endActiveBlock();
81
+ }
82
+ stream.send({
83
+ type: "data-rendering",
84
+ data: {
85
+ phase,
86
+ label,
87
+ },
88
+ });
89
+ },
78
90
  transcript(text, language) {
79
91
  stream.send({
80
92
  type: "transcript",
@@ -167,6 +179,9 @@ export function createSseEventEmitter(res, options = {}) {
167
179
  case "tool-call":
168
180
  stream.toolCall(event.data);
169
181
  break;
182
+ case "rendering":
183
+ stream.rendering(event.phase, event.label);
184
+ break;
170
185
  case "transcript":
171
186
  stream.transcript(event.text, event.language);
172
187
  break;
package/index.ts CHANGED
@@ -89,6 +89,9 @@ const createSessionBodySchema = z.object({
89
89
  triggerMessage: z.string().optional(),
90
90
  }).strict();
91
91
 
92
+ const VEGA_LITE_FENCE_START = "```vega-lite";
93
+ const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
94
+
92
95
  function isAbortError(error: unknown): boolean {
93
96
  return (
94
97
  error instanceof DOMException && error.name === "AbortError"
@@ -279,6 +282,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
279
282
 
280
283
  private async runAgentTurn(input: AgentTurnRunInput) {
281
284
  let fullResponse = "";
285
+ let bufferedTextDelta = "";
286
+ let isRenderingVegaLite = false;
282
287
  const maxTokens = this.options.maxTokens ?? 1000;
283
288
  const selectedMode = this.options.modes.find((mode) => mode.name === input.modeName) ?? this.options.modes[0];
284
289
  const [primaryModelSpec, summaryModelSpec] = await Promise.all([
@@ -386,13 +391,63 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
386
391
 
387
392
  if (textDelta) {
388
393
  fullResponse += textDelta;
394
+ bufferedTextDelta += textDelta;
395
+
396
+ if (
397
+ bufferedTextDelta.includes(VEGA_LITE_FENCE_START) &&
398
+ !COMPLETE_VEGA_LITE_BLOCK_RE.test(bufferedTextDelta)
399
+ ) {
400
+ if (!isRenderingVegaLite) {
401
+ isRenderingVegaLite = true;
402
+ await input.emit?.({
403
+ type: "rendering",
404
+ phase: "start",
405
+ label: "Rendering...",
406
+ });
407
+ }
408
+ continue;
409
+ }
410
+
411
+ if (isRenderingVegaLite) {
412
+ isRenderingVegaLite = false;
413
+ await input.emit?.({
414
+ type: "rendering",
415
+ phase: "end",
416
+ label: "Rendering...",
417
+ });
418
+ }
419
+
420
+ const streamableLength = bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
421
+ ? bufferedTextDelta.length
422
+ : bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(bufferedTextDelta);
423
+
424
+ if (!streamableLength) {
425
+ continue;
426
+ }
427
+
389
428
  await input.emit?.({
390
429
  type: "text-delta",
391
- delta: textDelta,
430
+ delta: bufferedTextDelta.slice(0, streamableLength),
392
431
  });
432
+ bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
393
433
  }
394
434
  }
395
435
 
436
+ if (isRenderingVegaLite) {
437
+ await input.emit?.({
438
+ type: "rendering",
439
+ phase: "end",
440
+ label: "Rendering...",
441
+ });
442
+ }
443
+
444
+ if (bufferedTextDelta) {
445
+ await input.emit?.({
446
+ type: "text-delta",
447
+ delta: bufferedTextDelta,
448
+ });
449
+ }
450
+
396
451
  return {
397
452
  text: fullResponse,
398
453
  };
@@ -956,3 +1011,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
956
1011
  })
957
1012
  }
958
1013
  }
1014
+
1015
+ function getPartialVegaLiteFenceStartLength(text: string): number {
1016
+ for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
1017
+ if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
1018
+ return length;
1019
+ }
1020
+ }
1021
+
1022
+ return 0;
1023
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.44.0",
3
+ "version": "1.44.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -108,6 +108,20 @@ function createAgentEventStream(
108
108
  });
109
109
  },
110
110
 
111
+ rendering(phase: "start" | "end", label: string) {
112
+ if (phase === "start") {
113
+ stream.endActiveBlock();
114
+ }
115
+
116
+ stream.send({
117
+ type: "data-rendering",
118
+ data: {
119
+ phase,
120
+ label,
121
+ },
122
+ });
123
+ },
124
+
111
125
  transcript(text: string, language?: string) {
112
126
  stream.send({
113
127
  type: "transcript",
@@ -226,6 +240,9 @@ export function createSseEventEmitter(
226
240
  case "tool-call":
227
241
  stream.toolCall(event.data);
228
242
  break;
243
+ case "rendering":
244
+ stream.rendering(event.phase, event.label);
245
+ break;
229
246
  case "transcript":
230
247
  stream.transcript(event.text, event.language);
231
248
  break;