@adminforth/agent 1.45.0 → 1.45.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/dist/index.js CHANGED
@@ -7,60 +7,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- var __asyncValues = (this && this.__asyncValues) || function (o) {
11
- if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
12
- var m = o[Symbol.asyncIterator], i;
13
- return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
14
- function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
15
- function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
16
- };
17
- import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
18
- import { randomUUID } from 'crypto';
19
- import { HumanMessage, SystemMessage } from "langchain";
10
+ import { AdminForthPlugin } from "adminforth";
20
11
  import { MemorySaver } from "@langchain/langgraph";
21
- import { z } from "zod";
22
- import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
23
12
  import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
24
- import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
25
- import { detectUserLanguage } from "./agent/languageDetect.js";
26
- import { prepareApiBasedTools as buildApiBasedTools } from './apiBasedTools.js';
27
- import { createSseEventEmitter } from "./surfaces/web-sse/createSseEventEmitter.js";
28
- import { appendCustomSystemPrompt, buildAgentSystemPrompt, buildAgentTurnSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT } from "./agent/systemPrompt.js";
29
- import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
30
- const agentResponseBodySchema = z.object({
31
- message: z.string(),
32
- sessionId: z.string(),
33
- mode: z.string().nullish(),
34
- timeZone: z.string().optional(),
35
- currentPage: z.custom().optional(),
36
- }).strict();
37
- const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({ message: true });
38
- const addSystemMessageBodySchema = z.object({
39
- sessionId: z.string(),
40
- systemMessage: z.string(),
41
- }).strict();
42
- const getSessionsBodySchema = z.object({
43
- limit: z.number().optional(),
44
- }).strict();
45
- const sessionIdBodySchema = z.object({
46
- sessionId: z.string(),
47
- }).strict();
48
- const createSessionBodySchema = z.object({
49
- triggerMessage: z.string().optional(),
50
- }).strict();
51
- const VEGA_LITE_FENCE_START = "```vega-lite";
52
- const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
53
- const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
54
- const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
55
- function isAbortError(error) {
56
- return (error instanceof DOMException && error.name === "AbortError") || (typeof error === "object" &&
57
- error !== null &&
58
- "name" in error &&
59
- (error.name === "AbortError" || error.name === "APIUserAbortError"));
60
- }
61
- function getErrorMessage(error) {
62
- return error instanceof Error ? error.message : String(error);
63
- }
13
+ import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT } from "./agent/systemPrompt.js";
14
+ import { setupCoreEndpoints } from "./endpoints/core.js";
15
+ import { setupSessionEndpoints } from "./endpoints/sessions.js";
16
+ import { setupChatSurfaceEndpoints } from "./endpoints/chatSurfaces.js";
17
+ import { AgentSessionStore } from "./sessionStore.js";
18
+ import { ChatSurfaceService } from "./chatSurfaceService.js";
19
+ import { AgentTurnService } from "./agentTurnService.js";
64
20
  export default class AdminForthAgentPlugin extends AdminForthPlugin {
65
21
  parseBody(schema, body, response) {
66
22
  const parsed = schema.safeParse(body !== null && body !== void 0 ? body : {});
@@ -70,57 +26,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
70
26
  }
71
27
  return parsed.data;
72
28
  }
73
- createNewTurn(sessionId, prompt, response) {
74
- return __awaiter(this, void 0, void 0, function* () {
75
- const turnId = randomUUID();
76
- const turnRecord = {
77
- [this.options.turnResource.idField]: turnId,
78
- [this.options.turnResource.sessionIdField]: sessionId,
79
- [this.options.turnResource.promptField]: prompt,
80
- [this.options.turnResource.responseField]: response || "not_finished",
81
- };
82
- const newTurn = yield this.adminforth.resource(this.options.turnResource.resourceId).create(turnRecord);
83
- return newTurn.createdRecord[this.options.turnResource.idField];
84
- });
85
- }
86
- getSessionTurns(sessionId) {
87
- return __awaiter(this, void 0, void 0, function* () {
88
- const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
89
- return turns.map(turn => ({
90
- prompt: turn[this.options.turnResource.promptField],
91
- response: turn[this.options.turnResource.responseField],
92
- }));
93
- });
94
- }
95
- getPreviousUserMessages(sessionId) {
96
- return __awaiter(this, void 0, void 0, function* () {
97
- const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 2, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
98
- return turns
99
- .reverse()
100
- .map((turn) => ({
101
- text: turn[this.options.turnResource.promptField],
102
- }));
103
- });
104
- }
105
- getChatSurfaceSessionId(incoming) {
106
- return `${incoming.surface}:${incoming.externalConversationId}`;
107
- }
108
- getOrCreateChatSurfaceSession(incoming, adminUser) {
109
- return __awaiter(this, void 0, void 0, function* () {
110
- const sessionId = this.getChatSurfaceSessionId(incoming);
111
- const sessionResource = this.adminforth.resource(this.options.sessionResource.resourceId);
112
- const session = yield sessionResource.get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
113
- if (session) {
114
- return sessionId;
115
- }
116
- yield sessionResource.create({
117
- [this.options.sessionResource.idField]: sessionId,
118
- [this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
119
- [this.options.sessionResource.askerIdField]: adminUser.pk,
120
- });
121
- return sessionId;
122
- });
123
- }
124
29
  getCheckpointer() {
125
30
  if (this.checkpointer)
126
31
  return this.checkpointer;
@@ -137,18 +42,22 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
137
42
  (_a = this.options.checkpointResource) === null || _a === void 0 ? void 0 : _a.resourceId,
138
43
  ].filter((resourceId) => Boolean(resourceId));
139
44
  }
140
- getChatSurfaceConnectActionAdapters() {
141
- var _a;
142
- return ((_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : [])
143
- .map((adapter) => adapter)
144
- .filter((adapter) => adapter.createConnectAction);
145
- }
146
45
  constructor(options) {
147
46
  super(options, import.meta.url);
148
47
  this.checkpointer = null;
149
- this.chatSurfaceLinkTokens = new Map();
150
48
  this.chatSurfaceSettingsPageRegistered = false;
151
49
  this.options = options;
50
+ this.sessionStore = new AgentSessionStore(() => this.adminforth, this.options);
51
+ this.agentTurnService = new AgentTurnService({
52
+ getAdminforth: () => this.adminforth,
53
+ getPluginInstanceId: () => this.pluginInstanceId,
54
+ options: this.options,
55
+ sessionStore: this.sessionStore,
56
+ getCheckpointer: this.getCheckpointer.bind(this),
57
+ getInternalAgentResourceIds: this.getInternalAgentResourceIds.bind(this),
58
+ getAgentSystemPrompt: () => this.agentSystemPromptPromise,
59
+ });
60
+ this.chatSurfaceService = new ChatSurfaceService(() => this.adminforth, this.options, this.sessionStore, this.agentTurnService.handleTurn.bind(this.agentTurnService));
152
61
  this.agentSystemPromptPromise = Promise.resolve(appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt));
153
62
  this.shouldHaveSingleInstancePerWholeApp = () => false;
154
63
  }
@@ -175,7 +84,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
175
84
  hasAudioAdapter: Boolean(this.options.audioAdapter),
176
85
  }
177
86
  });
178
- if (this.getChatSurfaceConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
87
+ if (this.chatSurfaceService.getConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
179
88
  if (!this.adminforth.config.auth.userMenuSettingsPages) {
180
89
  this.adminforth.config.auth.userMenuSettingsPages = [];
181
90
  }
@@ -219,812 +128,23 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
219
128
  instanceUniqueRepresentation(pluginOptions) {
220
129
  return `single`;
221
130
  }
222
- runAgentTurn(input) {
223
- return __awaiter(this, void 0, void 0, function* () {
224
- var _a, e_1, _b, _c;
225
- var _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
226
- let fullResponse = "";
227
- let bufferedTextDelta = "";
228
- let isRenderingVegaLite = false;
229
- const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d : 1000;
230
- const selectedMode = (_e = this.options.modes.find((mode) => mode.name === input.modeName)) !== null && _e !== void 0 ? _e : this.options.modes[0];
231
- const [primaryModelSpec, summaryModelSpec] = yield Promise.all([
232
- createAgentChatModel({
233
- adapter: selectedMode.completionAdapter,
234
- maxTokens,
235
- purpose: "primary",
236
- }),
237
- createAgentChatModel({
238
- adapter: selectedMode.completionAdapter,
239
- maxTokens,
240
- purpose: "summary",
241
- }),
242
- ]);
243
- const model = primaryModelSpec.model;
244
- const summaryModel = summaryModelSpec.model;
245
- const modelMiddleware = primaryModelSpec.middleware;
246
- const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
247
- .catch((error) => {
248
- var _a;
249
- if (((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) || isAbortError(error)) {
250
- throw error;
251
- }
252
- logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
253
- return null;
254
- });
255
- const systemPrompt = buildAgentTurnSystemPrompt({
256
- agentSystemPrompt: yield this.agentSystemPromptPromise,
257
- adminUser: input.adminUser,
258
- usernameField: this.adminforth.config.auth.usernameField,
259
- userLanguage,
260
- });
261
- const apiBasedTools = buildApiBasedTools(this.adminforth, this.getInternalAgentResourceIds());
262
- const stream = yield callAgent({
263
- name: `adminforth-agent-${this.pluginInstanceId}`,
264
- model,
265
- summaryModel,
266
- modelMiddleware,
267
- checkpointer: this.getCheckpointer(),
268
- messages: [
269
- new SystemMessage(systemPrompt),
270
- new HumanMessage(input.prompt),
271
- ],
272
- adminUser: input.adminUser,
273
- adminforth: this.adminforth,
274
- apiBasedTools,
275
- customComponentsDir: (_f = this.adminforth.config.customization.customComponentsDir) !== null && _f !== void 0 ? _f : "custom",
276
- sessionId: input.sessionId,
277
- turnId: input.turnId,
278
- currentPage: input.currentPage,
279
- userTimeZone: input.userTimeZone,
280
- abortSignal: input.abortSignal,
281
- emitToolCallEvent: (event) => {
282
- var _a;
283
- input.sequenceDebugCollector.handleToolCallEvent(event);
284
- void ((_a = input.emit) === null || _a === void 0 ? void 0 : _a.call(input, {
285
- type: "tool-call",
286
- data: event,
287
- }));
288
- },
289
- sequenceDebugSink: input.sequenceDebugCollector,
290
- });
291
- try {
292
- for (var _p = true, _q = __asyncValues(stream), _r; _r = yield _q.next(), _a = _r.done, !_a; _p = true) {
293
- _c = _r.value;
294
- _p = false;
295
- const rawChunk = _c;
296
- if ((_g = input.abortSignal) === null || _g === void 0 ? void 0 : _g.aborted) {
297
- throw new DOMException("This operation was aborted", "AbortError");
298
- }
299
- const [token, metadata] = rawChunk;
300
- const nodeName = typeof (metadata === null || metadata === void 0 ? void 0 : metadata.langgraph_node) === "string"
301
- ? metadata.langgraph_node
302
- : "";
303
- if (nodeName && !["model", "model_request"].includes(nodeName)) {
304
- continue;
305
- }
306
- const blocks = Array.isArray(token === null || token === void 0 ? void 0 : token.contentBlocks)
307
- ? token.contentBlocks
308
- : Array.isArray(token === null || token === void 0 ? void 0 : token.content)
309
- ? token.content
310
- : [];
311
- const reasoningDelta = blocks
312
- .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "reasoning")
313
- .map((b) => { var _a; return String((_a = b.reasoning) !== null && _a !== void 0 ? _a : ""); })
314
- .join("");
315
- const textDelta = blocks
316
- .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "text")
317
- .map((b) => { var _a; return String((_a = b.text) !== null && _a !== void 0 ? _a : ""); })
318
- .join("");
319
- if (reasoningDelta) {
320
- yield ((_h = input.emit) === null || _h === void 0 ? void 0 : _h.call(input, {
321
- type: "reasoning-delta",
322
- delta: reasoningDelta,
323
- }));
324
- }
325
- if (textDelta) {
326
- fullResponse += textDelta;
327
- bufferedTextDelta += textDelta;
328
- if (bufferedTextDelta.includes(VEGA_LITE_FENCE_START) &&
329
- !COMPLETE_VEGA_LITE_BLOCK_RE.test(bufferedTextDelta)) {
330
- if (!isRenderingVegaLite) {
331
- isRenderingVegaLite = true;
332
- yield ((_j = input.emit) === null || _j === void 0 ? void 0 : _j.call(input, {
333
- type: "rendering",
334
- phase: "start",
335
- label: "Rendering...",
336
- }));
337
- }
338
- continue;
339
- }
340
- if (isRenderingVegaLite) {
341
- isRenderingVegaLite = false;
342
- yield ((_k = input.emit) === null || _k === void 0 ? void 0 : _k.call(input, {
343
- type: "rendering",
344
- phase: "end",
345
- label: "Rendering...",
346
- }));
347
- }
348
- const streamableLength = bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
349
- ? bufferedTextDelta.length
350
- : bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(bufferedTextDelta);
351
- if (!streamableLength) {
352
- continue;
353
- }
354
- yield ((_l = input.emit) === null || _l === void 0 ? void 0 : _l.call(input, {
355
- type: "text-delta",
356
- delta: bufferedTextDelta.slice(0, streamableLength),
357
- }));
358
- bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
359
- }
360
- }
361
- }
362
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
363
- finally {
364
- try {
365
- if (!_p && !_a && (_b = _q.return)) yield _b.call(_q);
366
- }
367
- finally { if (e_1) throw e_1.error; }
368
- }
369
- if (isRenderingVegaLite) {
370
- yield ((_m = input.emit) === null || _m === void 0 ? void 0 : _m.call(input, {
371
- type: "rendering",
372
- phase: "end",
373
- label: "Rendering...",
374
- }));
375
- }
376
- if (bufferedTextDelta) {
377
- yield ((_o = input.emit) === null || _o === void 0 ? void 0 : _o.call(input, {
378
- type: "text-delta",
379
- delta: bufferedTextDelta,
380
- }));
381
- }
382
- return {
383
- text: fullResponse,
384
- };
385
- });
386
- }
387
- runAndPersistAgentResponse(input) {
388
- return __awaiter(this, void 0, void 0, function* () {
389
- var _a;
390
- const previousUserMessages = yield this.getPreviousUserMessages(input.sessionId);
391
- const turnId = yield this.createNewTurn(input.sessionId, input.prompt);
392
- yield this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
393
- [this.options.sessionResource.createdAtField]: new Date().toISOString(),
394
- });
395
- const sequenceDebugCollector = createSequenceDebugCollector();
396
- let fullResponse = "";
397
- let aborted = false;
398
- let failed = false;
399
- try {
400
- const agentResponse = yield this.runAgentTurn({
401
- prompt: input.prompt,
402
- sessionId: input.sessionId,
403
- turnId,
404
- previousUserMessages,
405
- modeName: input.modeName,
406
- userTimeZone: input.userTimeZone,
407
- currentPage: input.currentPage,
408
- abortSignal: input.abortSignal,
409
- adminUser: input.adminUser,
410
- sequenceDebugCollector,
411
- emit: input.emit,
412
- });
413
- fullResponse = agentResponse.text;
414
- }
415
- catch (error) {
416
- if (((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) || isAbortError(error)) {
417
- aborted = true;
418
- logger.info(input.abortLogMessage);
419
- }
420
- else {
421
- failed = true;
422
- fullResponse = getErrorMessage(error);
423
- logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
424
- }
425
- }
426
- sequenceDebugCollector.flush();
427
- const turnUpdates = {
428
- [this.options.turnResource.responseField]: fullResponse,
429
- };
430
- if (this.options.turnResource.debugField) {
431
- turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
432
- }
433
- yield this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, turnUpdates);
434
- return {
435
- text: fullResponse,
436
- turnId,
437
- aborted,
438
- failed,
439
- };
440
- });
441
- }
442
- handleTurn(input) {
443
- return __awaiter(this, void 0, void 0, function* () {
444
- var _a, _b;
445
- yield input.emit({
446
- type: "turn-started",
447
- messageId: randomUUID(),
448
- });
449
- const agentResponse = yield this.runAndPersistAgentResponse({
450
- prompt: input.prompt,
451
- sessionId: input.sessionId,
452
- modeName: input.modeName,
453
- userTimeZone: input.userTimeZone,
454
- currentPage: input.currentPage,
455
- abortSignal: input.abortSignal,
456
- adminUser: input.adminUser,
457
- emit: input.emit,
458
- failureLogMessage: (_a = input.failureLogMessage) !== null && _a !== void 0 ? _a : "Agent response failed",
459
- abortLogMessage: (_b = input.abortLogMessage) !== null && _b !== void 0 ? _b : "Agent response aborted",
460
- });
461
- if (agentResponse.failed) {
462
- yield input.emit({
463
- type: "error",
464
- error: agentResponse.text,
465
- });
466
- }
467
- else if (!agentResponse.aborted) {
468
- yield input.emit({
469
- type: "response",
470
- text: agentResponse.text,
471
- sessionId: input.sessionId,
472
- turnId: agentResponse.turnId,
473
- });
474
- }
475
- yield input.emit({
476
- type: "finish",
477
- });
478
- return agentResponse;
479
- });
480
- }
481
- createChatSurfaceEventEmitter(sink) {
482
- return (event) => __awaiter(this, void 0, void 0, function* () {
483
- if (event.type === "text-delta") {
484
- yield sink.emit({
485
- type: "text_delta",
486
- delta: event.delta,
487
- });
488
- return;
489
- }
490
- if (event.type === "response") {
491
- yield sink.emit({
492
- type: "done",
493
- text: event.text,
494
- });
495
- return;
496
- }
497
- if (event.type === "error") {
498
- yield sink.emit({
499
- type: "error",
500
- message: event.error,
501
- });
502
- }
503
- });
504
- }
505
- createChatSurfaceLinkToken(surface, adminUser) {
506
- for (const [token, payload] of this.chatSurfaceLinkTokens) {
507
- if (payload.expiresAt <= Date.now()) {
508
- this.chatSurfaceLinkTokens.delete(token);
509
- }
510
- }
511
- const token = randomUUID();
512
- this.chatSurfaceLinkTokens.set(token, {
513
- surface,
514
- adminUserId: adminUser.pk,
515
- expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
516
- });
517
- return token;
518
- }
519
- consumeChatSurfaceLinkToken(surface, token) {
520
- const payload = this.chatSurfaceLinkTokens.get(token);
521
- this.chatSurfaceLinkTokens.delete(token);
522
- if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
523
- return null;
524
- }
525
- return payload;
526
- }
527
- handleChatSurfaceLink(incoming, sink) {
528
- return __awaiter(this, void 0, void 0, function* () {
529
- var _a, _b, _c;
530
- if (typeof ((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.startPayload) !== "string") {
531
- return false;
532
- }
533
- const payload = this.consumeChatSurfaceLinkToken(incoming.surface, incoming.metadata.startPayload);
534
- if (!payload) {
535
- yield sink.emit({
536
- type: "error",
537
- message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
538
- });
539
- return true;
540
- }
541
- const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
542
- const authResourceId = this.adminforth.config.auth.usersResourceId;
543
- const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
544
- const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
545
- const adminUserRecord = yield this.adminforth.resource(authResourceId).get([
546
- Filters.EQ(primaryKeyField, payload.adminUserId),
547
- ]);
548
- yield this.adminforth.resource(authResourceId).update(payload.adminUserId, {
549
- [externalUserIdField]: Object.assign(Object.assign({}, ((_c = adminUserRecord[externalUserIdField]) !== null && _c !== void 0 ? _c : {})), { [incoming.surface]: incoming.externalUserId }),
550
- });
551
- yield sink.emit({
552
- type: "done",
553
- text: `${incoming.surface} account connected to AdminForth.`,
554
- });
555
- return true;
556
- });
557
- }
558
- handleChatSurfaceMessage(adapter, incoming, sink) {
559
- return __awaiter(this, void 0, void 0, function* () {
560
- var _a, _b;
561
- if (yield this.handleChatSurfaceLink(incoming, sink)) {
562
- return;
563
- }
564
- const authResourceId = this.adminforth.config.auth.usersResourceId;
565
- const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
566
- const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
567
- const externalUserIdField = (_a = this.options.chatExternalIdsField) !== null && _a !== void 0 ? _a : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
568
- const adminUserRecord = (yield this.adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))).find((user) => { var _a; return ((_a = user[externalUserIdField]) === null || _a === void 0 ? void 0 : _a[adapter.name]) === incoming.externalUserId; });
569
- if (!adminUserRecord) {
570
- yield sink.emit({
571
- type: "error",
572
- message: "This chat account is not authorized to use AdminForth Agent.",
573
- });
574
- return;
575
- }
576
- const adminUser = {
577
- pk: adminUserRecord[primaryKeyField],
578
- username: adminUserRecord[this.adminforth.config.auth.usernameField],
579
- dbUser: adminUserRecord,
580
- };
581
- yield this.handleTurn({
582
- prompt: incoming.prompt,
583
- sessionId: yield this.getOrCreateChatSurfaceSession(incoming, adminUser),
584
- modeName: incoming.modeName,
585
- userTimeZone: (_b = incoming.userTimeZone) !== null && _b !== void 0 ? _b : "UTC",
586
- adminUser,
587
- emit: this.createChatSurfaceEventEmitter(sink),
588
- failureLogMessage: `Agent ${incoming.surface} surface response failed`,
589
- abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
590
- });
591
- });
592
- }
593
131
  setupEndpoints(server) {
594
- var _a;
595
- if (this.getChatSurfaceConnectActionAdapters().length) {
596
- server.endpoint({
597
- method: "POST",
598
- path: "/agent/surfaces/connectable",
599
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
600
- var _b, _c;
601
- const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
602
- const externalIds = (_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {};
603
- return {
604
- surfaces: this.getChatSurfaceConnectActionAdapters().map((adapter) => {
605
- var _a;
606
- return ({
607
- name: adapter.name,
608
- externalUserId: (_a = externalIds[adapter.name]) !== null && _a !== void 0 ? _a : null,
609
- });
610
- }),
611
- };
612
- }),
613
- });
614
- }
615
- for (const adapter of (_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : []) {
616
- const connectActionAdapter = adapter;
617
- if (connectActionAdapter.createConnectAction) {
618
- server.endpoint({
619
- method: "POST",
620
- path: `/agent/surface/${adapter.name}/connect-action`,
621
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
622
- const token = this.createChatSurfaceLinkToken(adapter.name, adminUser);
623
- const action = yield connectActionAdapter.createConnectAction({ token });
624
- return {
625
- action,
626
- };
627
- }),
628
- });
629
- server.endpoint({
630
- method: "POST",
631
- path: `/agent/surface/${adapter.name}/disconnect`,
632
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
633
- var _b, _c;
634
- const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
635
- const externalIds = Object.assign({}, ((_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {}));
636
- delete externalIds[adapter.name];
637
- yield this.adminforth.resource(this.adminforth.config.auth.usersResourceId).update(adminUser.pk, {
638
- [externalUserIdField]: externalIds,
639
- });
640
- return {
641
- ok: true,
642
- };
643
- }),
644
- });
645
- }
646
- server.endpoint({
647
- method: "POST",
648
- noAuth: true,
649
- path: `/agent/surface/${adapter.name}/webhook`,
650
- handler: (ctx) => __awaiter(this, void 0, void 0, function* () {
651
- var _a;
652
- const surfaceContext = {
653
- body: ctx.body,
654
- headers: ctx.headers,
655
- abortSignal: ctx.abortSignal,
656
- rawRequest: ctx._raw_express_req,
657
- rawResponse: ctx._raw_express_res,
658
- };
659
- const incoming = yield adapter.parseIncomingMessage(surfaceContext);
660
- if (!incoming)
661
- return { ok: true };
662
- const sink = yield adapter.createEventSink(surfaceContext, incoming);
663
- try {
664
- yield this.handleChatSurfaceMessage(adapter, incoming, sink);
665
- }
666
- finally {
667
- yield ((_a = sink.close) === null || _a === void 0 ? void 0 : _a.call(sink));
668
- }
669
- return { ok: true };
670
- }),
671
- });
672
- }
673
- server.endpoint({
674
- method: 'POST',
675
- path: `/agent/get-placeholder-messages`,
676
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ headers, adminUser }) {
677
- if (!this.options.placeholderMessages) {
678
- return {
679
- messages: [],
680
- };
681
- }
682
- const messages = yield this.options.placeholderMessages({
683
- adminUser: adminUser,
684
- headers,
685
- });
686
- return {
687
- messages,
688
- };
689
- })
690
- });
691
- server.endpoint({
692
- method: 'POST',
693
- path: `/agent/response`,
694
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
695
- var _b;
696
- const data = this.parseBody(agentResponseBodySchema, body, response);
697
- if (!data)
698
- return;
699
- const emit = createSseEventEmitter(_raw_express_res, {
700
- vercelAiUiMessageStream: true,
701
- closeActiveBlockOnToolStart: true,
702
- });
703
- yield this.handleTurn({
704
- prompt: data.message,
705
- sessionId: data.sessionId,
706
- modeName: data.mode,
707
- userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
708
- currentPage: data.currentPage,
709
- abortSignal,
710
- adminUser: adminUser,
711
- emit,
712
- failureLogMessage: "Agent response streaming failed",
713
- abortLogMessage: "Agent response streaming aborted by the client",
714
- });
715
- return null;
716
- })
717
- });
718
- server.endpoint({
719
- method: 'POST',
720
- path: `/agent/speech-response`,
721
- target: 'upload',
722
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) {
723
- var _b;
724
- const req = _raw_express_req;
725
- const audioAdapter = this.options.audioAdapter;
726
- if (!audioAdapter) {
727
- response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
728
- return { error: "Audio adapter is not configured for AdminForth Agent" };
729
- }
730
- const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
731
- if (!data)
732
- return;
733
- if (!req.file) {
734
- response.setStatus(400, "Audio file is required");
735
- return { error: "Audio file is required" };
736
- }
737
- const emit = createSseEventEmitter(_raw_express_res);
738
- let transcription;
739
- try {
740
- transcription = yield audioAdapter.transcribe({
741
- buffer: req.file.buffer,
742
- filename: req.file.originalname,
743
- mimeType: req.file.mimetype,
744
- language: "auto",
745
- abortSignal,
746
- });
747
- }
748
- catch (error) {
749
- if (abortSignal.aborted || isAbortError(error)) {
750
- logger.info("Agent speech transcription aborted by the client");
751
- yield emit({ type: "finish" });
752
- return null;
753
- }
754
- logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
755
- yield emit({
756
- type: "error",
757
- error: "Speech transcription failed. Check server logs for details.",
758
- });
759
- yield emit({ type: "finish" });
760
- return null;
761
- }
762
- if (abortSignal.aborted) {
763
- yield emit({ type: "finish" });
764
- return null;
765
- }
766
- const prompt = transcription.text;
767
- if (!prompt) {
768
- yield emit({
769
- type: "error",
770
- error: "Speech transcription is empty",
771
- });
772
- yield emit({ type: "finish" });
773
- return null;
774
- }
775
- yield emit({
776
- type: "transcript",
777
- text: transcription.text,
778
- language: transcription.language,
779
- });
780
- const sessionId = data.sessionId;
781
- const currentPage = data.currentPage;
782
- const agentResponse = yield this.runAndPersistAgentResponse({
783
- prompt,
784
- sessionId,
785
- modeName: data.mode,
786
- userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
787
- currentPage,
788
- abortSignal,
789
- adminUser: adminUser,
790
- emit: (event) => __awaiter(this, void 0, void 0, function* () {
791
- if (event.type === "tool-call") {
792
- yield emit(event);
793
- }
794
- }),
795
- failureLogMessage: "Agent speech response failed",
796
- abortLogMessage: "Agent speech response aborted by the client",
797
- });
798
- if (agentResponse.aborted) {
799
- yield emit({ type: "finish" });
800
- return null;
801
- }
802
- if (agentResponse.failed) {
803
- yield emit({
804
- type: "error",
805
- error: agentResponse.text,
806
- });
807
- yield emit({ type: "finish" });
808
- return null;
809
- }
810
- try {
811
- yield emit({
812
- type: "speech-response",
813
- transcript: {
814
- text: transcription.text,
815
- language: transcription.language,
816
- },
817
- response: {
818
- text: agentResponse.text,
819
- },
820
- sessionId,
821
- turnId: agentResponse.turnId,
822
- });
823
- const speech = yield audioAdapter.synthesize({
824
- text: sanitizeSpeechText(agentResponse.text),
825
- stream: true,
826
- streamFormat: "audio",
827
- format: "pcm",
828
- abortSignal,
829
- });
830
- yield emit({
831
- type: "audio-start",
832
- mimeType: speech.mimeType,
833
- format: speech.format,
834
- sampleRate: 24000,
835
- channelCount: 1,
836
- bitsPerSample: 16,
837
- });
838
- const reader = speech.audioStream.getReader();
839
- const cancelAudioStream = () => {
840
- void reader.cancel().catch(() => undefined);
841
- };
842
- try {
843
- abortSignal.addEventListener("abort", cancelAudioStream, { once: true });
844
- while (true) {
845
- if (abortSignal.aborted) {
846
- yield reader.cancel().catch(() => undefined);
847
- break;
848
- }
849
- const { value, done } = yield reader.read();
850
- if (done) {
851
- break;
852
- }
853
- if (abortSignal.aborted) {
854
- break;
855
- }
856
- yield emit({
857
- type: "audio-delta",
858
- value,
859
- });
860
- }
861
- }
862
- finally {
863
- abortSignal.removeEventListener("abort", cancelAudioStream);
864
- reader.releaseLock();
865
- }
866
- yield emit({ type: "audio-done" });
867
- yield emit({ type: "finish" });
868
- return null;
869
- }
870
- catch (error) {
871
- if (abortSignal.aborted || isAbortError(error)) {
872
- logger.info("Agent speech audio streaming aborted by the client");
873
- }
874
- else {
875
- logger.error(`Agent speech audio streaming failed:\n${error}`);
876
- yield emit({
877
- type: "error",
878
- error: getErrorMessage(error),
879
- });
880
- }
881
- yield emit({ type: "finish" });
882
- return null;
883
- }
884
- })
885
- });
886
- server.endpoint({
887
- method: 'POST',
888
- path: `/agent/get-sessions`,
889
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
890
- var _b;
891
- const data = this.parseBody(getSessionsBodySchema, body, response);
892
- if (!data)
893
- return;
894
- const userId = adminUser.pk;
895
- const limit = (_b = data.limit) !== null && _b !== void 0 ? _b : 20;
896
- const sessions = yield this.adminforth.resource(this.options.sessionResource.resourceId).list([Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]);
897
- return {
898
- sessions: sessions.map((session) => ({
899
- sessionId: session[this.options.sessionResource.idField],
900
- title: session[this.options.sessionResource.titleField],
901
- timestamp: session[this.options.sessionResource.createdAtField],
902
- })),
903
- };
904
- })
905
- });
906
- server.endpoint({
907
- method: 'POST',
908
- path: `/agent/get-session-info`,
909
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
910
- const parsedBody = sessionIdBodySchema.safeParse(body);
911
- if (!parsedBody.success) {
912
- response.setStatus(422, parsedBody.error.message);
913
- return;
914
- }
915
- const userId = adminUser.pk;
916
- const sessionId = parsedBody.data.sessionId;
917
- const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
918
- if (!session) {
919
- return {
920
- error: 'Session not found'
921
- };
922
- }
923
- if (session[this.options.sessionResource.askerIdField] !== userId) {
924
- return {
925
- error: 'Unauthorized'
926
- };
927
- }
928
- const turns = yield this.getSessionTurns(sessionId);
929
- return {
930
- session: {
931
- sessionId,
932
- title: session[this.options.sessionResource.titleField],
933
- timestamp: session[this.options.sessionResource.createdAtField],
934
- messages: turns.flatMap(turn => {
935
- const messages = [];
936
- if (turn.prompt) {
937
- messages.push({
938
- text: turn.prompt,
939
- role: 'user',
940
- });
941
- }
942
- if (turn.response && turn.response !== "not_finished") {
943
- messages.push({
944
- text: turn.response,
945
- role: 'assistant',
946
- });
947
- }
948
- return messages;
949
- }),
950
- },
951
- };
952
- })
953
- });
954
- server.endpoint({
955
- method: 'POST',
956
- path: `/agent/create-session`,
957
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
958
- const data = this.parseBody(createSessionBodySchema, body, response);
959
- if (!data)
960
- return;
961
- const triggerMessage = data.triggerMessage;
962
- const userId = adminUser.pk;
963
- const title = (triggerMessage === null || triggerMessage === void 0 ? void 0 : triggerMessage.slice(0, 40)) || "New Session";
964
- const newSession = {
965
- [this.options.sessionResource.idField]: randomUUID(),
966
- [this.options.sessionResource.titleField]: title,
967
- [this.options.sessionResource.askerIdField]: userId,
968
- };
969
- yield this.adminforth.resource(this.options.sessionResource.resourceId).create(newSession);
970
- return {
971
- sessionId: newSession[this.options.sessionResource.idField],
972
- title: newSession[this.options.sessionResource.titleField],
973
- timestamp: newSession[this.options.sessionResource.createdAtField],
974
- messages: []
975
- };
976
- })
977
- });
978
- server.endpoint({
979
- method: 'POST',
980
- path: `/agent/delete-session`,
981
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
982
- const data = this.parseBody(sessionIdBodySchema, body, response);
983
- if (!data)
984
- return;
985
- const sessionId = data.sessionId;
986
- const userId = adminUser.pk;
987
- const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
988
- if (!session) {
989
- return {
990
- error: 'Session not found'
991
- };
992
- }
993
- if (session[this.options.sessionResource.askerIdField] !== userId) {
994
- return {
995
- error: 'Unauthorized'
996
- };
997
- }
998
- yield this.adminforth.resource(this.options.sessionResource.resourceId).delete(sessionId);
999
- const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)]);
1000
- for (const turn of turns) {
1001
- yield this.adminforth.resource(this.options.turnResource.resourceId).delete(turn[this.options.turnResource.idField]);
1002
- }
1003
- return {
1004
- ok: true
1005
- };
1006
- })
1007
- }),
1008
- server.endpoint({
1009
- method: 'POST',
1010
- path: `/agent/add-system-message-to-turns`,
1011
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
1012
- const data = this.parseBody(addSystemMessageBodySchema, body, response);
1013
- if (!data)
1014
- return;
1015
- yield this.createNewTurn(data.sessionId, data.systemMessage);
1016
- return {
1017
- ok: true
1018
- };
1019
- })
1020
- });
1021
- }
1022
- }
1023
- function getPartialVegaLiteFenceStartLength(text) {
1024
- for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
1025
- if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
1026
- return length;
1027
- }
132
+ const endpointContext = {
133
+ adminforth: this.adminforth,
134
+ options: this.options,
135
+ parseBody: this.parseBody.bind(this),
136
+ handleTurn: this.agentTurnService.handleTurn.bind(this.agentTurnService),
137
+ handleSpeechTurn: this.agentTurnService.handleSpeechTurn.bind(this.agentTurnService),
138
+ runAndPersistAgentResponse: this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService),
139
+ getSessionTurns: this.sessionStore.getSessionTurns.bind(this.sessionStore),
140
+ createNewTurn: this.sessionStore.createNewTurn.bind(this.sessionStore),
141
+ createSystemTurn: this.sessionStore.createSystemTurn.bind(this.sessionStore),
142
+ getChatSurfaceConnectActionAdapters: this.chatSurfaceService.getConnectActionAdapters.bind(this.chatSurfaceService),
143
+ createChatSurfaceLinkToken: this.chatSurfaceService.createLinkToken.bind(this.chatSurfaceService),
144
+ handleChatSurfaceMessage: this.chatSurfaceService.handleMessage.bind(this.chatSurfaceService),
145
+ };
146
+ setupCoreEndpoints(endpointContext, server);
147
+ setupSessionEndpoints(endpointContext, server);
148
+ setupChatSurfaceEndpoints(endpointContext, server);
1028
149
  }
1029
- return 0;
1030
150
  }