@adminforth/agent 1.43.28 → 1.44.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/agent/checkpointer.ts +2 -1
- package/agent/systemPrompt.ts +2 -1
- package/agentEvents.ts +61 -0
- package/agentResponseEvents.ts +1 -206
- package/apiBasedTools.ts +7 -3
- 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/systemPrompt.js +3 -1
- 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 +52 -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/apiBasedTools.js +5 -3
- package/dist/index.d.ts +58 -0
- package/dist/index.js +251 -59
- package/dist/sanitizeSpeechText.d.ts +1 -0
- package/dist/surfaces/web-sse/createSseEventEmitter.d.ts +14 -0
- package/dist/surfaces/web-sse/createSseEventEmitter.js +196 -0
- package/dist/types.d.ts +94 -0
- package/index.ts +279 -59
- package/package.json +2 -2
- package/surfaces/web-sse/createSseEventEmitter.ts +261 -0
- package/tsconfig.json +2 -1
- 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(),
|
|
@@ -91,6 +104,14 @@ function getErrorMessage(error: unknown): string {
|
|
|
91
104
|
return error instanceof Error ? error.message : String(error);
|
|
92
105
|
}
|
|
93
106
|
|
|
107
|
+
function requireAdminUser(adminUser: AdminUser | undefined): AdminUser {
|
|
108
|
+
if (!adminUser) {
|
|
109
|
+
throw new Error("AdminForth Agent endpoint requires an authenticated admin user");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return adminUser;
|
|
113
|
+
}
|
|
114
|
+
|
|
94
115
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
95
116
|
options: PluginOptions;
|
|
96
117
|
agentSystemPromptPromise: Promise<string>;
|
|
@@ -98,7 +119,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
98
119
|
private parseBody<T>(
|
|
99
120
|
schema: z.ZodType<T>,
|
|
100
121
|
body: unknown,
|
|
101
|
-
response: { setStatus: (code: number, message
|
|
122
|
+
response: { setStatus: (code: number, message: string) => void },
|
|
102
123
|
): T | null {
|
|
103
124
|
const parsed = schema.safeParse(body ?? {});
|
|
104
125
|
if (!parsed.success) {
|
|
@@ -146,6 +167,33 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
146
167
|
}));
|
|
147
168
|
}
|
|
148
169
|
|
|
170
|
+
private getChatSurfaceSessionId(incoming: ChatSurfaceIncomingMessage) {
|
|
171
|
+
return `${incoming.surface}:${incoming.externalConversationId}`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private async getOrCreateChatSurfaceSession(
|
|
175
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
176
|
+
adminUser: AdminUser,
|
|
177
|
+
) {
|
|
178
|
+
const sessionId = this.getChatSurfaceSessionId(incoming);
|
|
179
|
+
const sessionResource = this.adminforth.resource(this.options.sessionResource.resourceId);
|
|
180
|
+
const session = await sessionResource.get(
|
|
181
|
+
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (session) {
|
|
185
|
+
return sessionId;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await sessionResource.create({
|
|
189
|
+
[this.options.sessionResource.idField]: sessionId,
|
|
190
|
+
[this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
|
|
191
|
+
[this.options.sessionResource.askerIdField]: adminUser.pk,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return sessionId;
|
|
195
|
+
}
|
|
196
|
+
|
|
149
197
|
private getCheckpointer() {
|
|
150
198
|
if (this.checkpointer) return this.checkpointer;
|
|
151
199
|
|
|
@@ -215,6 +263,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
215
263
|
|
|
216
264
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
217
265
|
this.options.audioAdapter?.validate();
|
|
266
|
+
for (const chatSurfaceAdapter of this.options.chatSurfaceAdapters ?? []) {
|
|
267
|
+
chatSurfaceAdapter.validate();
|
|
268
|
+
}
|
|
218
269
|
this.agentSystemPromptPromise = buildAgentSystemPrompt(
|
|
219
270
|
adminforth,
|
|
220
271
|
this.getInternalAgentResourceIds(),
|
|
@@ -258,7 +309,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
258
309
|
const systemPrompt = buildAgentTurnSystemPrompt({
|
|
259
310
|
agentSystemPrompt: await this.agentSystemPromptPromise,
|
|
260
311
|
adminUser: input.adminUser,
|
|
261
|
-
usernameField: this.adminforth.config.auth
|
|
312
|
+
usernameField: this.adminforth.config.auth!.usernameField,
|
|
262
313
|
userLanguage,
|
|
263
314
|
});
|
|
264
315
|
const apiBasedTools = buildApiBasedTools(
|
|
@@ -279,7 +330,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
279
330
|
adminUser: input.adminUser,
|
|
280
331
|
adminforth: this.adminforth,
|
|
281
332
|
apiBasedTools,
|
|
282
|
-
|
|
333
|
+
customComponentsDir: this.adminforth.config.customization.customComponentsDir ?? "custom",
|
|
283
334
|
sessionId: input.sessionId,
|
|
284
335
|
turnId: input.turnId,
|
|
285
336
|
currentPage: input.currentPage,
|
|
@@ -287,7 +338,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
287
338
|
abortSignal: input.abortSignal,
|
|
288
339
|
emitToolCallEvent: (event) => {
|
|
289
340
|
input.sequenceDebugCollector.handleToolCallEvent(event);
|
|
290
|
-
input.
|
|
341
|
+
void input.emit?.({
|
|
342
|
+
type: "tool-call",
|
|
343
|
+
data: event,
|
|
344
|
+
});
|
|
291
345
|
},
|
|
292
346
|
sequenceDebugSink: input.sequenceDebugCollector,
|
|
293
347
|
});
|
|
@@ -324,12 +378,18 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
324
378
|
.join("");
|
|
325
379
|
|
|
326
380
|
if (reasoningDelta) {
|
|
327
|
-
input.
|
|
381
|
+
await input.emit?.({
|
|
382
|
+
type: "reasoning-delta",
|
|
383
|
+
delta: reasoningDelta,
|
|
384
|
+
});
|
|
328
385
|
}
|
|
329
386
|
|
|
330
387
|
if (textDelta) {
|
|
331
388
|
fullResponse += textDelta;
|
|
332
|
-
input.
|
|
389
|
+
await input.emit?.({
|
|
390
|
+
type: "text-delta",
|
|
391
|
+
delta: textDelta,
|
|
392
|
+
});
|
|
333
393
|
}
|
|
334
394
|
}
|
|
335
395
|
|
|
@@ -361,9 +421,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
361
421
|
abortSignal: input.abortSignal,
|
|
362
422
|
adminUser: input.adminUser,
|
|
363
423
|
sequenceDebugCollector,
|
|
364
|
-
|
|
365
|
-
emitReasoningDelta: input.emitReasoningDelta,
|
|
366
|
-
emitTextDelta: input.emitTextDelta,
|
|
424
|
+
emit: input.emit,
|
|
367
425
|
});
|
|
368
426
|
fullResponse = agentResponse.text;
|
|
369
427
|
} catch (error) {
|
|
@@ -374,7 +432,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
374
432
|
failed = true;
|
|
375
433
|
fullResponse = getErrorMessage(error);
|
|
376
434
|
logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
|
|
377
|
-
input.emitErrorResponse?.(fullResponse);
|
|
378
435
|
}
|
|
379
436
|
}
|
|
380
437
|
|
|
@@ -397,11 +454,140 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
397
454
|
};
|
|
398
455
|
}
|
|
399
456
|
|
|
457
|
+
async handleTurn(input: HandleTurnInput) {
|
|
458
|
+
await input.emit({
|
|
459
|
+
type: "turn-started",
|
|
460
|
+
messageId: randomUUID(),
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const agentResponse = await this.runAndPersistAgentResponse({
|
|
464
|
+
prompt: input.prompt,
|
|
465
|
+
sessionId: input.sessionId,
|
|
466
|
+
modeName: input.modeName,
|
|
467
|
+
userTimeZone: input.userTimeZone,
|
|
468
|
+
currentPage: input.currentPage,
|
|
469
|
+
abortSignal: input.abortSignal,
|
|
470
|
+
adminUser: input.adminUser,
|
|
471
|
+
emit: input.emit,
|
|
472
|
+
failureLogMessage: input.failureLogMessage ?? "Agent response failed",
|
|
473
|
+
abortLogMessage: input.abortLogMessage ?? "Agent response aborted",
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
if (agentResponse.failed) {
|
|
477
|
+
await input.emit({
|
|
478
|
+
type: "error",
|
|
479
|
+
error: agentResponse.text,
|
|
480
|
+
});
|
|
481
|
+
} else if (!agentResponse.aborted) {
|
|
482
|
+
await input.emit({
|
|
483
|
+
type: "response",
|
|
484
|
+
text: agentResponse.text,
|
|
485
|
+
sessionId: input.sessionId,
|
|
486
|
+
turnId: agentResponse.turnId,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
await input.emit({
|
|
491
|
+
type: "finish",
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
return agentResponse;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private createChatSurfaceEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
|
|
498
|
+
return async (event) => {
|
|
499
|
+
if (event.type === "text-delta") {
|
|
500
|
+
await sink.emit({
|
|
501
|
+
type: "text_delta",
|
|
502
|
+
delta: event.delta,
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (event.type === "response") {
|
|
508
|
+
await sink.emit({
|
|
509
|
+
type: "done",
|
|
510
|
+
text: event.text,
|
|
511
|
+
});
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (event.type === "error") {
|
|
516
|
+
await sink.emit({
|
|
517
|
+
type: "error",
|
|
518
|
+
message: event.error,
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private async handleChatSurfaceMessage(
|
|
525
|
+
adapter: ChatSurfaceAdapter,
|
|
526
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
527
|
+
sink: ChatSurfaceEventSink,
|
|
528
|
+
) {
|
|
529
|
+
const adminUser = await adapter.resolveAdminUser({
|
|
530
|
+
adminforth: this.adminforth,
|
|
531
|
+
incoming,
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
if (!adminUser) {
|
|
535
|
+
await sink.emit({
|
|
536
|
+
type: "error",
|
|
537
|
+
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
538
|
+
});
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
await this.handleTurn({
|
|
543
|
+
prompt: incoming.prompt,
|
|
544
|
+
sessionId: await this.getOrCreateChatSurfaceSession(incoming, adminUser),
|
|
545
|
+
modeName: incoming.modeName,
|
|
546
|
+
userTimeZone: incoming.userTimeZone ?? "UTC",
|
|
547
|
+
adminUser,
|
|
548
|
+
emit: this.createChatSurfaceEventEmitter(sink),
|
|
549
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
550
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
400
554
|
setupEndpoints(server: IHttpServer) {
|
|
555
|
+
for (const adapter of this.options.chatSurfaceAdapters ?? []) {
|
|
556
|
+
server.endpoint({
|
|
557
|
+
method: "POST",
|
|
558
|
+
noAuth: true,
|
|
559
|
+
path: `/agent/surface/${adapter.name}/webhook`,
|
|
560
|
+
handler: async (ctx) => {
|
|
561
|
+
const surfaceContext = {
|
|
562
|
+
body: ctx.body,
|
|
563
|
+
headers: ctx.headers,
|
|
564
|
+
abortSignal: ctx.abortSignal,
|
|
565
|
+
rawRequest: ctx._raw_express_req,
|
|
566
|
+
rawResponse: ctx._raw_express_res,
|
|
567
|
+
};
|
|
568
|
+
const incoming = await adapter.parseIncomingMessage(surfaceContext);
|
|
569
|
+
|
|
570
|
+
if (!incoming) return { ok: true };
|
|
571
|
+
|
|
572
|
+
const sink = await adapter.createEventSink(surfaceContext, incoming);
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
await this.handleChatSurfaceMessage(adapter, incoming, sink);
|
|
576
|
+
} finally {
|
|
577
|
+
await sink.close?.();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return { ok: true };
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
401
585
|
server.endpoint({
|
|
402
586
|
method: 'POST',
|
|
403
587
|
path: `/agent/get-placeholder-messages`,
|
|
404
588
|
handler: async ({ headers, adminUser }) => {
|
|
589
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
590
|
+
|
|
405
591
|
if (!this.options.placeholderMessages) {
|
|
406
592
|
return {
|
|
407
593
|
messages: [],
|
|
@@ -409,7 +595,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
409
595
|
}
|
|
410
596
|
|
|
411
597
|
const messages = await this.options.placeholderMessages({
|
|
412
|
-
adminUser,
|
|
598
|
+
adminUser: currentAdminUser,
|
|
413
599
|
headers,
|
|
414
600
|
});
|
|
415
601
|
|
|
@@ -422,28 +608,26 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
422
608
|
method: 'POST',
|
|
423
609
|
path: `/agent/response`,
|
|
424
610
|
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
611
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
425
612
|
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
426
613
|
if (!data) return;
|
|
427
|
-
const
|
|
428
|
-
|
|
614
|
+
const emit = createSseEventEmitter(_raw_express_res, {
|
|
615
|
+
vercelAiUiMessageStream: true,
|
|
616
|
+
closeActiveBlockOnToolStart: true,
|
|
617
|
+
});
|
|
429
618
|
|
|
430
|
-
|
|
431
|
-
await this.runAndPersistAgentResponse({
|
|
619
|
+
await this.handleTurn({
|
|
432
620
|
prompt: data.message,
|
|
433
621
|
sessionId: data.sessionId,
|
|
434
622
|
modeName: data.mode,
|
|
435
623
|
userTimeZone: data.timeZone ?? 'UTC',
|
|
436
624
|
currentPage: data.currentPage,
|
|
437
625
|
abortSignal,
|
|
438
|
-
adminUser,
|
|
439
|
-
|
|
440
|
-
emitReasoningDelta: stream.reasoningDelta,
|
|
441
|
-
emitTextDelta: stream.textDelta,
|
|
442
|
-
emitErrorResponse: stream.textDelta,
|
|
626
|
+
adminUser: currentAdminUser,
|
|
627
|
+
emit,
|
|
443
628
|
failureLogMessage: "Agent response streaming failed",
|
|
444
629
|
abortLogMessage: "Agent response streaming aborted by the client",
|
|
445
630
|
});
|
|
446
|
-
stream.end();
|
|
447
631
|
return null;
|
|
448
632
|
}
|
|
449
633
|
});
|
|
@@ -452,19 +636,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
452
636
|
path: `/agent/speech-response`,
|
|
453
637
|
target: 'upload',
|
|
454
638
|
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
639
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
455
640
|
const req = _raw_express_req as ExpressMulterRequest;
|
|
456
641
|
const audioAdapter = this.options.audioAdapter;
|
|
457
642
|
if (!audioAdapter) {
|
|
458
|
-
response.setStatus(400,
|
|
643
|
+
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
459
644
|
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
460
645
|
}
|
|
461
646
|
const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
462
647
|
if (!data) return;
|
|
463
648
|
if (!req.file) {
|
|
464
|
-
response.setStatus(400,
|
|
649
|
+
response.setStatus(400, "Audio file is required");
|
|
465
650
|
return { error: "Audio file is required" };
|
|
466
651
|
}
|
|
467
|
-
const
|
|
652
|
+
const emit = createSseEventEmitter(_raw_express_res);
|
|
468
653
|
|
|
469
654
|
let transcription;
|
|
470
655
|
|
|
@@ -479,28 +664,38 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
479
664
|
} catch (error) {
|
|
480
665
|
if (abortSignal.aborted || isAbortError(error)) {
|
|
481
666
|
logger.info("Agent speech transcription aborted by the client");
|
|
482
|
-
|
|
667
|
+
await emit({ type: "finish" });
|
|
483
668
|
return null;
|
|
484
669
|
}
|
|
485
670
|
|
|
486
671
|
logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
|
|
487
|
-
|
|
488
|
-
|
|
672
|
+
await emit({
|
|
673
|
+
type: "error",
|
|
674
|
+
error: "Speech transcription failed. Check server logs for details.",
|
|
675
|
+
});
|
|
676
|
+
await emit({ type: "finish" });
|
|
489
677
|
return null;
|
|
490
678
|
}
|
|
491
679
|
|
|
492
680
|
if (abortSignal.aborted) {
|
|
493
|
-
|
|
681
|
+
await emit({ type: "finish" });
|
|
494
682
|
return null;
|
|
495
683
|
}
|
|
496
684
|
|
|
497
685
|
const prompt = transcription.text;
|
|
498
686
|
if (!prompt) {
|
|
499
|
-
|
|
500
|
-
|
|
687
|
+
await emit({
|
|
688
|
+
type: "error",
|
|
689
|
+
error: "Speech transcription is empty",
|
|
690
|
+
});
|
|
691
|
+
await emit({ type: "finish" });
|
|
501
692
|
return null;
|
|
502
693
|
}
|
|
503
|
-
|
|
694
|
+
await emit({
|
|
695
|
+
type: "transcript",
|
|
696
|
+
text: transcription.text,
|
|
697
|
+
language: transcription.language,
|
|
698
|
+
});
|
|
504
699
|
|
|
505
700
|
const sessionId = data.sessionId as string;
|
|
506
701
|
const currentPage = data.currentPage;
|
|
@@ -511,35 +706,43 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
511
706
|
userTimeZone: data.timeZone ?? 'UTC',
|
|
512
707
|
currentPage,
|
|
513
708
|
abortSignal,
|
|
514
|
-
adminUser,
|
|
515
|
-
|
|
709
|
+
adminUser: currentAdminUser,
|
|
710
|
+
emit: async (event) => {
|
|
711
|
+
if (event.type === "tool-call") {
|
|
712
|
+
await emit(event);
|
|
713
|
+
}
|
|
714
|
+
},
|
|
516
715
|
failureLogMessage: "Agent speech response failed",
|
|
517
716
|
abortLogMessage: "Agent speech response aborted by the client",
|
|
518
717
|
});
|
|
519
718
|
|
|
520
719
|
if (agentResponse.aborted) {
|
|
521
|
-
|
|
720
|
+
await emit({ type: "finish" });
|
|
522
721
|
return null;
|
|
523
722
|
}
|
|
524
723
|
|
|
525
724
|
if (agentResponse.failed) {
|
|
526
|
-
|
|
527
|
-
|
|
725
|
+
await emit({
|
|
726
|
+
type: "error",
|
|
727
|
+
error: agentResponse.text,
|
|
728
|
+
});
|
|
729
|
+
await emit({ type: "finish" });
|
|
528
730
|
return null;
|
|
529
731
|
}
|
|
530
732
|
|
|
531
733
|
try {
|
|
532
|
-
|
|
533
|
-
|
|
734
|
+
await emit({
|
|
735
|
+
type: "speech-response",
|
|
736
|
+
transcript: {
|
|
534
737
|
text: transcription.text,
|
|
535
738
|
language: transcription.language,
|
|
536
739
|
},
|
|
537
|
-
{
|
|
740
|
+
response: {
|
|
538
741
|
text: agentResponse.text,
|
|
539
742
|
},
|
|
540
743
|
sessionId,
|
|
541
|
-
agentResponse.turnId,
|
|
542
|
-
);
|
|
744
|
+
turnId: agentResponse.turnId,
|
|
745
|
+
});
|
|
543
746
|
const speech = await audioAdapter.synthesize({
|
|
544
747
|
text: sanitizeSpeechText(agentResponse.text),
|
|
545
748
|
stream: true,
|
|
@@ -548,7 +751,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
548
751
|
abortSignal,
|
|
549
752
|
});
|
|
550
753
|
|
|
551
|
-
|
|
754
|
+
await emit({
|
|
755
|
+
type: "audio-start",
|
|
756
|
+
mimeType: speech.mimeType,
|
|
757
|
+
format: speech.format,
|
|
758
|
+
sampleRate: 24000,
|
|
759
|
+
channelCount: 1,
|
|
760
|
+
bitsPerSample: 16,
|
|
761
|
+
});
|
|
552
762
|
|
|
553
763
|
const reader = speech.audioStream.getReader();
|
|
554
764
|
const cancelAudioStream = () => {
|
|
@@ -574,24 +784,30 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
574
784
|
break;
|
|
575
785
|
}
|
|
576
786
|
|
|
577
|
-
|
|
787
|
+
await emit({
|
|
788
|
+
type: "audio-delta",
|
|
789
|
+
value,
|
|
790
|
+
});
|
|
578
791
|
}
|
|
579
792
|
} finally {
|
|
580
793
|
abortSignal.removeEventListener("abort", cancelAudioStream);
|
|
581
794
|
reader.releaseLock();
|
|
582
795
|
}
|
|
583
796
|
|
|
584
|
-
|
|
585
|
-
|
|
797
|
+
await emit({ type: "audio-done" });
|
|
798
|
+
await emit({ type: "finish" });
|
|
586
799
|
return null;
|
|
587
800
|
} catch (error) {
|
|
588
801
|
if (abortSignal.aborted || isAbortError(error)) {
|
|
589
802
|
logger.info("Agent speech audio streaming aborted by the client");
|
|
590
803
|
} else {
|
|
591
804
|
logger.error(`Agent speech audio streaming failed:\n${error}`);
|
|
592
|
-
|
|
805
|
+
await emit({
|
|
806
|
+
type: "error",
|
|
807
|
+
error: getErrorMessage(error),
|
|
808
|
+
});
|
|
593
809
|
}
|
|
594
|
-
|
|
810
|
+
await emit({ type: "finish" });
|
|
595
811
|
return null;
|
|
596
812
|
}
|
|
597
813
|
}
|
|
@@ -600,9 +816,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
600
816
|
method: 'POST',
|
|
601
817
|
path: `/agent/get-sessions`,
|
|
602
818
|
handler: async ({body, adminUser, response }) => {
|
|
819
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
603
820
|
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
604
821
|
if (!data) return;
|
|
605
|
-
const userId =
|
|
822
|
+
const userId = currentAdminUser.pk;
|
|
606
823
|
const limit = data.limit ?? 20;
|
|
607
824
|
const sessions = await this.adminforth.resource(this.options.sessionResource.resourceId).list(
|
|
608
825
|
[Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]
|
|
@@ -620,12 +837,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
620
837
|
method: 'POST',
|
|
621
838
|
path: `/agent/get-session-info`,
|
|
622
839
|
handler: async ({body, adminUser, response }) => {
|
|
840
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
623
841
|
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
624
842
|
if (!parsedBody.success) {
|
|
625
843
|
response.setStatus(422, parsedBody.error.message);
|
|
626
844
|
return;
|
|
627
845
|
}
|
|
628
|
-
const userId =
|
|
846
|
+
const userId = currentAdminUser.pk;
|
|
629
847
|
const sessionId = parsedBody.data.sessionId;
|
|
630
848
|
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
631
849
|
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
@@ -647,7 +865,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
647
865
|
title: session[this.options.sessionResource.titleField],
|
|
648
866
|
timestamp: session[this.options.sessionResource.createdAtField],
|
|
649
867
|
messages: turns.flatMap(turn => {
|
|
650
|
-
const messages = [];
|
|
868
|
+
const messages: Array<{ text: string; role: 'user' | 'assistant' }> = [];
|
|
651
869
|
if (turn.prompt) {
|
|
652
870
|
messages.push({
|
|
653
871
|
text: turn.prompt,
|
|
@@ -670,10 +888,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
670
888
|
method: 'POST',
|
|
671
889
|
path: `/agent/create-session`,
|
|
672
890
|
handler: async ({body, adminUser, response }) => {
|
|
891
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
673
892
|
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
674
893
|
if (!data) return;
|
|
675
894
|
const triggerMessage = data.triggerMessage;
|
|
676
|
-
const userId =
|
|
895
|
+
const userId = currentAdminUser.pk;
|
|
677
896
|
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
678
897
|
const newSession = {
|
|
679
898
|
[this.options.sessionResource.idField]: randomUUID(),
|
|
@@ -693,10 +912,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
693
912
|
method: 'POST',
|
|
694
913
|
path: `/agent/delete-session`,
|
|
695
914
|
handler: async ({body, adminUser, response }) => {
|
|
915
|
+
const currentAdminUser = requireAdminUser(adminUser);
|
|
696
916
|
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
697
917
|
if (!data) return;
|
|
698
918
|
const sessionId = data.sessionId;
|
|
699
|
-
const userId =
|
|
919
|
+
const userId = currentAdminUser.pk;
|
|
700
920
|
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
701
921
|
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
702
922
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
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",
|