@adminforth/agent 1.43.29 → 1.44.1
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 +66 -0
- package/agentResponseEvents.ts +1 -206
- package/build.log +2 -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/agent/checkpointer.d.ts +29 -0
- package/dist/agent/languageDetect.d.ts +10 -0
- package/dist/agent/middleware/apiBasedTools.d.ts +3 -0
- package/dist/agent/middleware/openAiResponsesContinuation.d.ts +1 -0
- package/dist/agent/middleware/sequenceDebug.d.ts +46 -0
- package/dist/agent/simpleAgent.d.ts +61 -0
- package/dist/agent/skills/registry.d.ts +13 -0
- package/dist/agent/systemPrompt.d.ts +11 -0
- package/dist/agent/toolCallEvents.d.ts +27 -0
- package/dist/agent/tools/apiTool.d.ts +6 -0
- package/dist/agent/tools/fetchSkill.d.ts +8 -0
- package/dist/agent/tools/fetchToolSchema.d.ts +9 -0
- package/dist/agent/tools/getUserLocation.d.ts +8 -0
- package/dist/agent/tools/index.d.ts +4 -0
- package/dist/agentEvents.d.ts +56 -0
- package/dist/agentEvents.js +1 -0
- package/dist/agentResponseEvents.d.ts +1 -0
- package/dist/agentResponseEvents.js +1 -144
- package/dist/apiBasedTools.d.ts +29 -0
- 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.d.ts +58 -0
- package/dist/index.js +280 -48
- package/dist/sanitizeSpeechText.d.ts +1 -0
- package/dist/surfaces/web-sse/createSseEventEmitter.d.ts +14 -0
- package/dist/surfaces/web-sse/createSseEventEmitter.js +211 -0
- package/dist/types.d.ts +94 -0
- package/index.ts +315 -46
- package/package.json +2 -2
- package/surfaces/web-sse/createSseEventEmitter.ts +278 -0
- package/tsconfig.json +1 -0
- package/types.ts +6 -0
package/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AdminUser,
|
|
3
|
+
AdminForthResource,
|
|
4
|
+
ChatSurfaceAdapter,
|
|
5
|
+
ChatSurfaceEventSink,
|
|
6
|
+
ChatSurfaceIncomingMessage,
|
|
7
|
+
IAdminForth,
|
|
8
|
+
IHttpServer,
|
|
9
|
+
} from "adminforth";
|
|
2
10
|
|
|
3
11
|
import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
|
|
4
12
|
|
|
@@ -12,12 +20,14 @@ import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
|
12
20
|
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
|
|
13
21
|
import { detectUserLanguage, type PreviousUserMessage } from "./agent/languageDetect.js";
|
|
14
22
|
import { prepareApiBasedTools as buildApiBasedTools } from './apiBasedTools.js';
|
|
15
|
-
import {
|
|
23
|
+
import type { AgentEventEmitter } from "./agentEvents.js";
|
|
24
|
+
import { createSseEventEmitter } from "./surfaces/web-sse/createSseEventEmitter.js";
|
|
16
25
|
import { appendCustomSystemPrompt, buildAgentSystemPrompt, buildAgentTurnSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
|
|
17
|
-
import type { ToolCallEvent } from "./agent/toolCallEvents.js";
|
|
18
26
|
import type { CurrentPageContext } from "./agent/tools/getUserLocation.js";
|
|
19
27
|
import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
|
|
20
28
|
|
|
29
|
+
export type { AgentEvent, AgentEventEmitter } from "./agentEvents.js";
|
|
30
|
+
|
|
21
31
|
type MulterFile = {
|
|
22
32
|
buffer: Buffer;
|
|
23
33
|
originalname: string;
|
|
@@ -37,18 +47,21 @@ type AgentTurnRunInput = {
|
|
|
37
47
|
abortSignal?: AbortSignal;
|
|
38
48
|
adminUser: AdminUser;
|
|
39
49
|
sequenceDebugCollector: ReturnType<typeof createSequenceDebugCollector>;
|
|
40
|
-
|
|
41
|
-
emitTextDelta?: (delta: string) => void;
|
|
42
|
-
emitToolCallEvent?: (event: ToolCallEvent) => void;
|
|
50
|
+
emit?: AgentEventEmitter;
|
|
43
51
|
};
|
|
44
52
|
|
|
45
53
|
type RunAndPersistAgentResponseInput =
|
|
46
54
|
Omit<AgentTurnRunInput, "turnId" | "sequenceDebugCollector" | "previousUserMessages"> & {
|
|
47
|
-
emitErrorResponse?: (response: string) => void;
|
|
48
55
|
failureLogMessage: string;
|
|
49
56
|
abortLogMessage: string;
|
|
50
57
|
};
|
|
51
58
|
|
|
59
|
+
type HandleTurnInput = Omit<RunAndPersistAgentResponseInput, "failureLogMessage" | "abortLogMessage"> & {
|
|
60
|
+
emit: AgentEventEmitter;
|
|
61
|
+
failureLogMessage?: string;
|
|
62
|
+
abortLogMessage?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
52
65
|
const agentResponseBodySchema = z.object({
|
|
53
66
|
message: z.string(),
|
|
54
67
|
sessionId: z.string(),
|
|
@@ -76,6 +89,9 @@ const createSessionBodySchema = z.object({
|
|
|
76
89
|
triggerMessage: z.string().optional(),
|
|
77
90
|
}).strict();
|
|
78
91
|
|
|
92
|
+
const VEGA_LITE_FENCE_START = "```vega-lite";
|
|
93
|
+
const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
|
|
94
|
+
|
|
79
95
|
function isAbortError(error: unknown): boolean {
|
|
80
96
|
return (
|
|
81
97
|
error instanceof DOMException && error.name === "AbortError"
|
|
@@ -154,6 +170,33 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
154
170
|
}));
|
|
155
171
|
}
|
|
156
172
|
|
|
173
|
+
private getChatSurfaceSessionId(incoming: ChatSurfaceIncomingMessage) {
|
|
174
|
+
return `${incoming.surface}:${incoming.externalConversationId}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async getOrCreateChatSurfaceSession(
|
|
178
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
179
|
+
adminUser: AdminUser,
|
|
180
|
+
) {
|
|
181
|
+
const sessionId = this.getChatSurfaceSessionId(incoming);
|
|
182
|
+
const sessionResource = this.adminforth.resource(this.options.sessionResource.resourceId);
|
|
183
|
+
const session = await sessionResource.get(
|
|
184
|
+
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (session) {
|
|
188
|
+
return sessionId;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
await sessionResource.create({
|
|
192
|
+
[this.options.sessionResource.idField]: sessionId,
|
|
193
|
+
[this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
|
|
194
|
+
[this.options.sessionResource.askerIdField]: adminUser.pk,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return sessionId;
|
|
198
|
+
}
|
|
199
|
+
|
|
157
200
|
private getCheckpointer() {
|
|
158
201
|
if (this.checkpointer) return this.checkpointer;
|
|
159
202
|
|
|
@@ -223,6 +266,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
223
266
|
|
|
224
267
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
225
268
|
this.options.audioAdapter?.validate();
|
|
269
|
+
for (const chatSurfaceAdapter of this.options.chatSurfaceAdapters ?? []) {
|
|
270
|
+
chatSurfaceAdapter.validate();
|
|
271
|
+
}
|
|
226
272
|
this.agentSystemPromptPromise = buildAgentSystemPrompt(
|
|
227
273
|
adminforth,
|
|
228
274
|
this.getInternalAgentResourceIds(),
|
|
@@ -236,6 +282,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
236
282
|
|
|
237
283
|
private async runAgentTurn(input: AgentTurnRunInput) {
|
|
238
284
|
let fullResponse = "";
|
|
285
|
+
let bufferedTextDelta = "";
|
|
286
|
+
let isRenderingVegaLite = false;
|
|
239
287
|
const maxTokens = this.options.maxTokens ?? 1000;
|
|
240
288
|
const selectedMode = this.options.modes.find((mode) => mode.name === input.modeName) ?? this.options.modes[0];
|
|
241
289
|
const [primaryModelSpec, summaryModelSpec] = await Promise.all([
|
|
@@ -295,7 +343,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
295
343
|
abortSignal: input.abortSignal,
|
|
296
344
|
emitToolCallEvent: (event) => {
|
|
297
345
|
input.sequenceDebugCollector.handleToolCallEvent(event);
|
|
298
|
-
input.
|
|
346
|
+
void input.emit?.({
|
|
347
|
+
type: "tool-call",
|
|
348
|
+
data: event,
|
|
349
|
+
});
|
|
299
350
|
},
|
|
300
351
|
sequenceDebugSink: input.sequenceDebugCollector,
|
|
301
352
|
});
|
|
@@ -332,15 +383,71 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
332
383
|
.join("");
|
|
333
384
|
|
|
334
385
|
if (reasoningDelta) {
|
|
335
|
-
input.
|
|
386
|
+
await input.emit?.({
|
|
387
|
+
type: "reasoning-delta",
|
|
388
|
+
delta: reasoningDelta,
|
|
389
|
+
});
|
|
336
390
|
}
|
|
337
391
|
|
|
338
392
|
if (textDelta) {
|
|
339
393
|
fullResponse += textDelta;
|
|
340
|
-
|
|
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
|
+
|
|
428
|
+
await input.emit?.({
|
|
429
|
+
type: "text-delta",
|
|
430
|
+
delta: bufferedTextDelta.slice(0, streamableLength),
|
|
431
|
+
});
|
|
432
|
+
bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
|
|
341
433
|
}
|
|
342
434
|
}
|
|
343
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
|
+
|
|
344
451
|
return {
|
|
345
452
|
text: fullResponse,
|
|
346
453
|
};
|
|
@@ -369,9 +476,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
369
476
|
abortSignal: input.abortSignal,
|
|
370
477
|
adminUser: input.adminUser,
|
|
371
478
|
sequenceDebugCollector,
|
|
372
|
-
|
|
373
|
-
emitReasoningDelta: input.emitReasoningDelta,
|
|
374
|
-
emitTextDelta: input.emitTextDelta,
|
|
479
|
+
emit: input.emit,
|
|
375
480
|
});
|
|
376
481
|
fullResponse = agentResponse.text;
|
|
377
482
|
} catch (error) {
|
|
@@ -382,7 +487,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
382
487
|
failed = true;
|
|
383
488
|
fullResponse = getErrorMessage(error);
|
|
384
489
|
logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
|
|
385
|
-
input.emitErrorResponse?.(fullResponse);
|
|
386
490
|
}
|
|
387
491
|
}
|
|
388
492
|
|
|
@@ -405,7 +509,134 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
405
509
|
};
|
|
406
510
|
}
|
|
407
511
|
|
|
512
|
+
async handleTurn(input: HandleTurnInput) {
|
|
513
|
+
await input.emit({
|
|
514
|
+
type: "turn-started",
|
|
515
|
+
messageId: randomUUID(),
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const agentResponse = await this.runAndPersistAgentResponse({
|
|
519
|
+
prompt: input.prompt,
|
|
520
|
+
sessionId: input.sessionId,
|
|
521
|
+
modeName: input.modeName,
|
|
522
|
+
userTimeZone: input.userTimeZone,
|
|
523
|
+
currentPage: input.currentPage,
|
|
524
|
+
abortSignal: input.abortSignal,
|
|
525
|
+
adminUser: input.adminUser,
|
|
526
|
+
emit: input.emit,
|
|
527
|
+
failureLogMessage: input.failureLogMessage ?? "Agent response failed",
|
|
528
|
+
abortLogMessage: input.abortLogMessage ?? "Agent response aborted",
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
if (agentResponse.failed) {
|
|
532
|
+
await input.emit({
|
|
533
|
+
type: "error",
|
|
534
|
+
error: agentResponse.text,
|
|
535
|
+
});
|
|
536
|
+
} else if (!agentResponse.aborted) {
|
|
537
|
+
await input.emit({
|
|
538
|
+
type: "response",
|
|
539
|
+
text: agentResponse.text,
|
|
540
|
+
sessionId: input.sessionId,
|
|
541
|
+
turnId: agentResponse.turnId,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
await input.emit({
|
|
546
|
+
type: "finish",
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
return agentResponse;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private createChatSurfaceEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
|
|
553
|
+
return async (event) => {
|
|
554
|
+
if (event.type === "text-delta") {
|
|
555
|
+
await sink.emit({
|
|
556
|
+
type: "text_delta",
|
|
557
|
+
delta: event.delta,
|
|
558
|
+
});
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
if (event.type === "response") {
|
|
563
|
+
await sink.emit({
|
|
564
|
+
type: "done",
|
|
565
|
+
text: event.text,
|
|
566
|
+
});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (event.type === "error") {
|
|
571
|
+
await sink.emit({
|
|
572
|
+
type: "error",
|
|
573
|
+
message: event.error,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private async handleChatSurfaceMessage(
|
|
580
|
+
adapter: ChatSurfaceAdapter,
|
|
581
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
582
|
+
sink: ChatSurfaceEventSink,
|
|
583
|
+
) {
|
|
584
|
+
const adminUser = await adapter.resolveAdminUser({
|
|
585
|
+
adminforth: this.adminforth,
|
|
586
|
+
incoming,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
if (!adminUser) {
|
|
590
|
+
await sink.emit({
|
|
591
|
+
type: "error",
|
|
592
|
+
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
await this.handleTurn({
|
|
598
|
+
prompt: incoming.prompt,
|
|
599
|
+
sessionId: await this.getOrCreateChatSurfaceSession(incoming, adminUser),
|
|
600
|
+
modeName: incoming.modeName,
|
|
601
|
+
userTimeZone: incoming.userTimeZone ?? "UTC",
|
|
602
|
+
adminUser,
|
|
603
|
+
emit: this.createChatSurfaceEventEmitter(sink),
|
|
604
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
605
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
408
609
|
setupEndpoints(server: IHttpServer) {
|
|
610
|
+
for (const adapter of this.options.chatSurfaceAdapters ?? []) {
|
|
611
|
+
server.endpoint({
|
|
612
|
+
method: "POST",
|
|
613
|
+
noAuth: true,
|
|
614
|
+
path: `/agent/surface/${adapter.name}/webhook`,
|
|
615
|
+
handler: async (ctx) => {
|
|
616
|
+
const surfaceContext = {
|
|
617
|
+
body: ctx.body,
|
|
618
|
+
headers: ctx.headers,
|
|
619
|
+
abortSignal: ctx.abortSignal,
|
|
620
|
+
rawRequest: ctx._raw_express_req,
|
|
621
|
+
rawResponse: ctx._raw_express_res,
|
|
622
|
+
};
|
|
623
|
+
const incoming = await adapter.parseIncomingMessage(surfaceContext);
|
|
624
|
+
|
|
625
|
+
if (!incoming) return { ok: true };
|
|
626
|
+
|
|
627
|
+
const sink = await adapter.createEventSink(surfaceContext, incoming);
|
|
628
|
+
|
|
629
|
+
try {
|
|
630
|
+
await this.handleChatSurfaceMessage(adapter, incoming, sink);
|
|
631
|
+
} finally {
|
|
632
|
+
await sink.close?.();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return { ok: true };
|
|
636
|
+
},
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
409
640
|
server.endpoint({
|
|
410
641
|
method: 'POST',
|
|
411
642
|
path: `/agent/get-placeholder-messages`,
|
|
@@ -435,11 +666,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
435
666
|
const currentAdminUser = requireAdminUser(adminUser);
|
|
436
667
|
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
437
668
|
if (!data) return;
|
|
438
|
-
const
|
|
439
|
-
|
|
669
|
+
const emit = createSseEventEmitter(_raw_express_res, {
|
|
670
|
+
vercelAiUiMessageStream: true,
|
|
671
|
+
closeActiveBlockOnToolStart: true,
|
|
672
|
+
});
|
|
440
673
|
|
|
441
|
-
|
|
442
|
-
await this.runAndPersistAgentResponse({
|
|
674
|
+
await this.handleTurn({
|
|
443
675
|
prompt: data.message,
|
|
444
676
|
sessionId: data.sessionId,
|
|
445
677
|
modeName: data.mode,
|
|
@@ -447,14 +679,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
447
679
|
currentPage: data.currentPage,
|
|
448
680
|
abortSignal,
|
|
449
681
|
adminUser: currentAdminUser,
|
|
450
|
-
|
|
451
|
-
emitReasoningDelta: stream.reasoningDelta,
|
|
452
|
-
emitTextDelta: stream.textDelta,
|
|
453
|
-
emitErrorResponse: stream.textDelta,
|
|
682
|
+
emit,
|
|
454
683
|
failureLogMessage: "Agent response streaming failed",
|
|
455
684
|
abortLogMessage: "Agent response streaming aborted by the client",
|
|
456
685
|
});
|
|
457
|
-
stream.end();
|
|
458
686
|
return null;
|
|
459
687
|
}
|
|
460
688
|
});
|
|
@@ -476,7 +704,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
476
704
|
response.setStatus(400, "Audio file is required");
|
|
477
705
|
return { error: "Audio file is required" };
|
|
478
706
|
}
|
|
479
|
-
const
|
|
707
|
+
const emit = createSseEventEmitter(_raw_express_res);
|
|
480
708
|
|
|
481
709
|
let transcription;
|
|
482
710
|
|
|
@@ -491,28 +719,38 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
491
719
|
} catch (error) {
|
|
492
720
|
if (abortSignal.aborted || isAbortError(error)) {
|
|
493
721
|
logger.info("Agent speech transcription aborted by the client");
|
|
494
|
-
|
|
722
|
+
await emit({ type: "finish" });
|
|
495
723
|
return null;
|
|
496
724
|
}
|
|
497
725
|
|
|
498
726
|
logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
|
|
499
|
-
|
|
500
|
-
|
|
727
|
+
await emit({
|
|
728
|
+
type: "error",
|
|
729
|
+
error: "Speech transcription failed. Check server logs for details.",
|
|
730
|
+
});
|
|
731
|
+
await emit({ type: "finish" });
|
|
501
732
|
return null;
|
|
502
733
|
}
|
|
503
734
|
|
|
504
735
|
if (abortSignal.aborted) {
|
|
505
|
-
|
|
736
|
+
await emit({ type: "finish" });
|
|
506
737
|
return null;
|
|
507
738
|
}
|
|
508
739
|
|
|
509
740
|
const prompt = transcription.text;
|
|
510
741
|
if (!prompt) {
|
|
511
|
-
|
|
512
|
-
|
|
742
|
+
await emit({
|
|
743
|
+
type: "error",
|
|
744
|
+
error: "Speech transcription is empty",
|
|
745
|
+
});
|
|
746
|
+
await emit({ type: "finish" });
|
|
513
747
|
return null;
|
|
514
748
|
}
|
|
515
|
-
|
|
749
|
+
await emit({
|
|
750
|
+
type: "transcript",
|
|
751
|
+
text: transcription.text,
|
|
752
|
+
language: transcription.language,
|
|
753
|
+
});
|
|
516
754
|
|
|
517
755
|
const sessionId = data.sessionId as string;
|
|
518
756
|
const currentPage = data.currentPage;
|
|
@@ -524,34 +762,42 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
524
762
|
currentPage,
|
|
525
763
|
abortSignal,
|
|
526
764
|
adminUser: currentAdminUser,
|
|
527
|
-
|
|
765
|
+
emit: async (event) => {
|
|
766
|
+
if (event.type === "tool-call") {
|
|
767
|
+
await emit(event);
|
|
768
|
+
}
|
|
769
|
+
},
|
|
528
770
|
failureLogMessage: "Agent speech response failed",
|
|
529
771
|
abortLogMessage: "Agent speech response aborted by the client",
|
|
530
772
|
});
|
|
531
773
|
|
|
532
774
|
if (agentResponse.aborted) {
|
|
533
|
-
|
|
775
|
+
await emit({ type: "finish" });
|
|
534
776
|
return null;
|
|
535
777
|
}
|
|
536
778
|
|
|
537
779
|
if (agentResponse.failed) {
|
|
538
|
-
|
|
539
|
-
|
|
780
|
+
await emit({
|
|
781
|
+
type: "error",
|
|
782
|
+
error: agentResponse.text,
|
|
783
|
+
});
|
|
784
|
+
await emit({ type: "finish" });
|
|
540
785
|
return null;
|
|
541
786
|
}
|
|
542
787
|
|
|
543
788
|
try {
|
|
544
|
-
|
|
545
|
-
|
|
789
|
+
await emit({
|
|
790
|
+
type: "speech-response",
|
|
791
|
+
transcript: {
|
|
546
792
|
text: transcription.text,
|
|
547
793
|
language: transcription.language,
|
|
548
794
|
},
|
|
549
|
-
{
|
|
795
|
+
response: {
|
|
550
796
|
text: agentResponse.text,
|
|
551
797
|
},
|
|
552
798
|
sessionId,
|
|
553
|
-
agentResponse.turnId,
|
|
554
|
-
);
|
|
799
|
+
turnId: agentResponse.turnId,
|
|
800
|
+
});
|
|
555
801
|
const speech = await audioAdapter.synthesize({
|
|
556
802
|
text: sanitizeSpeechText(agentResponse.text),
|
|
557
803
|
stream: true,
|
|
@@ -560,7 +806,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
560
806
|
abortSignal,
|
|
561
807
|
});
|
|
562
808
|
|
|
563
|
-
|
|
809
|
+
await emit({
|
|
810
|
+
type: "audio-start",
|
|
811
|
+
mimeType: speech.mimeType,
|
|
812
|
+
format: speech.format,
|
|
813
|
+
sampleRate: 24000,
|
|
814
|
+
channelCount: 1,
|
|
815
|
+
bitsPerSample: 16,
|
|
816
|
+
});
|
|
564
817
|
|
|
565
818
|
const reader = speech.audioStream.getReader();
|
|
566
819
|
const cancelAudioStream = () => {
|
|
@@ -586,24 +839,30 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
586
839
|
break;
|
|
587
840
|
}
|
|
588
841
|
|
|
589
|
-
|
|
842
|
+
await emit({
|
|
843
|
+
type: "audio-delta",
|
|
844
|
+
value,
|
|
845
|
+
});
|
|
590
846
|
}
|
|
591
847
|
} finally {
|
|
592
848
|
abortSignal.removeEventListener("abort", cancelAudioStream);
|
|
593
849
|
reader.releaseLock();
|
|
594
850
|
}
|
|
595
851
|
|
|
596
|
-
|
|
597
|
-
|
|
852
|
+
await emit({ type: "audio-done" });
|
|
853
|
+
await emit({ type: "finish" });
|
|
598
854
|
return null;
|
|
599
855
|
} catch (error) {
|
|
600
856
|
if (abortSignal.aborted || isAbortError(error)) {
|
|
601
857
|
logger.info("Agent speech audio streaming aborted by the client");
|
|
602
858
|
} else {
|
|
603
859
|
logger.error(`Agent speech audio streaming failed:\n${error}`);
|
|
604
|
-
|
|
860
|
+
await emit({
|
|
861
|
+
type: "error",
|
|
862
|
+
error: getErrorMessage(error),
|
|
863
|
+
});
|
|
605
864
|
}
|
|
606
|
-
|
|
865
|
+
await emit({ type: "finish" });
|
|
607
866
|
return null;
|
|
608
867
|
}
|
|
609
868
|
}
|
|
@@ -752,3 +1011,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
752
1011
|
})
|
|
753
1012
|
}
|
|
754
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.
|
|
3
|
+
"version": "1.44.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@langchain/core": "^1.1.40",
|
|
34
34
|
"@langchain/langgraph": "^1.2.8",
|
|
35
35
|
"@langchain/langgraph-checkpoint": "^1.0.1",
|
|
36
|
-
"adminforth": "
|
|
36
|
+
"adminforth": ">=2.54.0",
|
|
37
37
|
"dayjs": "^1.11.20",
|
|
38
38
|
"langchain": "^1.3.3",
|
|
39
39
|
"multer": "^2.1.1",
|