@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 +5 -0
- package/build.log +2 -2
- package/custom/ChatSurface.vue +7 -1
- package/custom/composables/useAgentStore.ts +5 -2
- package/custom/conversation_area/ProcessingTimeline.vue +23 -2
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +2 -2
- package/custom/types.ts +5 -4
- package/dist/agentEvents.d.ts +4 -0
- package/dist/custom/ChatSurface.vue +7 -1
- package/dist/custom/composables/useAgentStore.ts +5 -2
- package/dist/custom/conversation_area/ProcessingTimeline.vue +23 -2
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +2 -2
- package/dist/custom/types.ts +5 -4
- package/dist/index.js +60 -7
- package/dist/surfaces/web-sse/createSseEventEmitter.js +15 -0
- package/index.ts +66 -1
- package/package.json +1 -1
- package/surfaces/web-sse/createSseEventEmitter.ts +17 -0
package/agentEvents.ts
CHANGED
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,
|
|
66
|
-
total size is 1,
|
|
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
|
package/custom/ChatSurface.vue
CHANGED
|
@@ -106,7 +106,13 @@ onMounted(async () => {
|
|
|
106
106
|
if( coreStore.isMobile ) {
|
|
107
107
|
agentStore.setIsTeleportedToBody(false);
|
|
108
108
|
} else {
|
|
109
|
-
|
|
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
|
-
|
|
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) =>
|
|
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'
|
|
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
|
|
3
|
-
toolName
|
|
4
|
-
phase
|
|
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/agentEvents.d.ts
CHANGED
|
@@ -106,7 +106,13 @@ onMounted(async () => {
|
|
|
106
106
|
if( coreStore.isMobile ) {
|
|
107
107
|
agentStore.setIsTeleportedToBody(false);
|
|
108
108
|
} else {
|
|
109
|
-
|
|
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
|
-
|
|
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) =>
|
|
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'
|
|
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/dist/custom/types.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
export interface IPartData {
|
|
2
|
-
toolCallId
|
|
3
|
-
toolName
|
|
4
|
-
phase
|
|
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
|
|
272
|
-
_c =
|
|
273
|
-
|
|
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
|
-
|
|
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:
|
|
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 (!
|
|
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:
|
|
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
|
@@ -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;
|