@adminforth/agent 1.34.2 → 1.36.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/dist/index.js CHANGED
@@ -47,6 +47,19 @@ function formatAgentError(error) {
47
47
  }
48
48
  return String(error);
49
49
  }
50
+ function formatAgentResponseError(error) {
51
+ if (isAggregateErrorLike(error)) {
52
+ const nestedErrors = error.errors.map(formatAgentResponseError);
53
+ if (nestedErrors.length) {
54
+ return nestedErrors.join("\n");
55
+ }
56
+ return error.message || "Agent response failed";
57
+ }
58
+ if (error instanceof Error) {
59
+ return error.toString();
60
+ }
61
+ return String(error);
62
+ }
50
63
  function formatAdminUserPrompt(adminUser, usernameField) {
51
64
  const dbUser = adminUser.dbUser;
52
65
  const adminUserContext = {
@@ -59,16 +72,6 @@ function formatAdminUserPrompt(adminUser, usernameField) {
59
72
  "Use this admin user email when the user asks to send information to themselves, the current admin, or the logged-in user.",
60
73
  ].join("\n");
61
74
  }
62
- function formatCurrentPagePrompt(currentPage) {
63
- if (!currentPage) {
64
- return null;
65
- }
66
- return [
67
- "Current user page context for the latest message:",
68
- JSON.stringify(currentPage, null, 2),
69
- "When the user says here, this page, current page, or opened page, treat it as this page.",
70
- ].join("\n");
71
- }
72
75
  function assertRequiredApiTool(apiBasedTools, toolName) {
73
76
  if (toolName in apiBasedTools) {
74
77
  return;
@@ -191,6 +194,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
191
194
  modes: this.options.modes.map((mode) => ({ name: mode.name })),
192
195
  defaultModeName: this.options.modes[0].name,
193
196
  stickByDefault: (_b = this.options.stickByDefault) !== null && _b !== void 0 ? _b : false,
197
+ hasAudioAdapter: Boolean(this.options.audioAdapter),
194
198
  }
195
199
  });
196
200
  if (!this.pluginOptions.sessionResource) {
@@ -199,12 +203,110 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
199
203
  });
200
204
  }
201
205
  validateConfigAfterDiscover(adminforth, resourceConfig) {
206
+ var _a;
207
+ (_a = this.options.audioAdapter) === null || _a === void 0 ? void 0 : _a.validate();
202
208
  this.agentSystemPromptPromise = buildAgentSystemPrompt(adminforth, this.getInternalAgentResourceIds())
203
209
  .then((systemPrompt) => appendCustomSystemPrompt(systemPrompt, this.options.systemPrompt));
204
210
  }
205
211
  instanceUniqueRepresentation(pluginOptions) {
206
212
  return `single`;
207
213
  }
214
+ runAgentTurn(input) {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ var _a, e_1, _b, _c;
217
+ var _d, _e, _f, _g;
218
+ let fullResponse = "";
219
+ const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d : 10000;
220
+ const selectedMode = (_e = this.options.modes.find((mode) => mode.name === input.modeName)) !== null && _e !== void 0 ? _e : this.options.modes[0];
221
+ const { model, summaryModel, modelMiddleware } = yield this.getModeModels(selectedMode, maxTokens);
222
+ const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt)
223
+ .catch((error) => {
224
+ logger.warn(`Failed to detect user language: ${error instanceof Error ? error.message : String(error)}`);
225
+ return null;
226
+ });
227
+ const systemPrompt = [
228
+ yield this.agentSystemPromptPromise,
229
+ formatAdminUserPrompt(input.adminUser, this.adminforth.config.auth.usernameField),
230
+ formatLanguagePrompt(userLanguage),
231
+ ].join("\n\n");
232
+ const apiBasedTools = buildApiBasedTools(this.adminforth, this.getInternalAgentResourceIds());
233
+ for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
234
+ assertRequiredApiTool(apiBasedTools, toolName);
235
+ }
236
+ assertRequiredApiTool(apiBasedTools, "update_record");
237
+ this.apiBasedTools = apiBasedTools;
238
+ const stream = yield callAgent({
239
+ name: `adminforth-agent-${this.pluginInstanceId}`,
240
+ model,
241
+ summaryModel,
242
+ modelMiddleware,
243
+ checkpointer: this.getCheckpointer(),
244
+ messages: [
245
+ new SystemMessage(systemPrompt),
246
+ new HumanMessage(input.prompt),
247
+ ],
248
+ adminUser: input.adminUser,
249
+ adminforth: this.adminforth,
250
+ apiBasedTools,
251
+ customComponentsDir: this.adminforth.config.customization.customComponentsDir,
252
+ sessionId: input.sessionId,
253
+ turnId: input.turnId,
254
+ currentPage: input.currentPage,
255
+ httpExtra: input.httpExtra,
256
+ userTimeZone: input.userTimeZone,
257
+ emitToolCallEvent: (event) => {
258
+ var _a;
259
+ input.sequenceDebugCollector.handleToolCallEvent(event);
260
+ (_a = input.emitToolCallEvent) === null || _a === void 0 ? void 0 : _a.call(input, event);
261
+ },
262
+ sequenceDebugSink: input.sequenceDebugCollector,
263
+ });
264
+ try {
265
+ for (var _h = true, _j = __asyncValues(stream), _k; _k = yield _j.next(), _a = _k.done, !_a; _h = true) {
266
+ _c = _k.value;
267
+ _h = false;
268
+ const rawChunk = _c;
269
+ const [token, metadata] = rawChunk;
270
+ const nodeName = typeof (metadata === null || metadata === void 0 ? void 0 : metadata.langgraph_node) === "string"
271
+ ? metadata.langgraph_node
272
+ : "";
273
+ if (nodeName && !["model", "model_request"].includes(nodeName)) {
274
+ continue;
275
+ }
276
+ const blocks = Array.isArray(token === null || token === void 0 ? void 0 : token.contentBlocks)
277
+ ? token.contentBlocks
278
+ : Array.isArray(token === null || token === void 0 ? void 0 : token.content)
279
+ ? token.content
280
+ : [];
281
+ const reasoningDelta = blocks
282
+ .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "reasoning")
283
+ .map((b) => { var _a; return String((_a = b.reasoning) !== null && _a !== void 0 ? _a : ""); })
284
+ .join("");
285
+ const textDelta = blocks
286
+ .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "text")
287
+ .map((b) => { var _a; return String((_a = b.text) !== null && _a !== void 0 ? _a : ""); })
288
+ .join("");
289
+ if (reasoningDelta) {
290
+ (_f = input.emitReasoningDelta) === null || _f === void 0 ? void 0 : _f.call(input, reasoningDelta);
291
+ }
292
+ if (textDelta) {
293
+ fullResponse += textDelta;
294
+ (_g = input.emitTextDelta) === null || _g === void 0 ? void 0 : _g.call(input, textDelta);
295
+ }
296
+ }
297
+ }
298
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
299
+ finally {
300
+ try {
301
+ if (!_h && !_a && (_b = _j.return)) yield _b.call(_j);
302
+ }
303
+ finally { if (e_1) throw e_1.error; }
304
+ }
305
+ return {
306
+ text: fullResponse,
307
+ };
308
+ });
309
+ }
208
310
  setupEndpoints(server) {
209
311
  server.endpoint({
210
312
  method: 'POST',
@@ -234,13 +336,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
234
336
  server.endpoint({
235
337
  method: 'POST',
236
338
  path: `/agent/response`,
237
- handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, query, headers, cookies, adminUser, response, requestUrl, _raw_express_res }) {
238
- var _b, e_1, _c, _d;
239
- var _e, _f, _g;
339
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, query, headers, cookies, adminUser, response, requestUrl, _raw_express_res, abortSignal }) {
340
+ var _b;
240
341
  const res = _raw_express_res;
241
342
  const messageId = randomUUID();
242
343
  const prompt = body.message;
243
- const userTimeZone = (_e = body.timeZone) !== null && _e !== void 0 ? _e : 'UTC';
344
+ const userTimeZone = (_b = body.timeZone) !== null && _b !== void 0 ? _b : 'UTC';
244
345
  const currentPage = body.currentPage;
245
346
  const sessionId = body.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
246
347
  const turnId = yield this.createNewTurn(sessionId, prompt);
@@ -264,7 +365,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
264
365
  if (event.phase === "start") {
265
366
  endActiveBlock();
266
367
  }
267
- sequenceDebugCollector.handleToolCallEvent(event);
268
368
  send({
269
369
  type: "data-tool-call",
270
370
  data: event,
@@ -311,43 +411,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
311
411
  type: 'start',
312
412
  messageId,
313
413
  });
314
- const maxTokens = (_f = this.options.maxTokens) !== null && _f !== void 0 ? _f : 10000;
315
- const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
316
- const { model, summaryModel, modelMiddleware } = yield this.getModeModels(selectedMode, maxTokens);
317
- const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, prompt)
318
- .catch((error) => {
319
- logger.warn(`Failed to detect user language: ${error instanceof Error ? error.message : String(error)}`);
320
- return null;
321
- });
322
- const systemPrompt = [
323
- yield this.agentSystemPromptPromise,
324
- formatAdminUserPrompt(adminUser, this.adminforth.config.auth.usernameField),
325
- formatLanguagePrompt(userLanguage),
326
- ].join("\n\n");
327
- const apiBasedTools = buildApiBasedTools(this.adminforth, this.getInternalAgentResourceIds());
328
- for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
329
- assertRequiredApiTool(apiBasedTools, toolName);
330
- }
331
- assertRequiredApiTool(apiBasedTools, "update_record");
332
- this.apiBasedTools = apiBasedTools;
333
- const currentPagePrompt = formatCurrentPagePrompt(currentPage);
334
- const stream = yield callAgent({
335
- name: `adminforth-agent-${this.pluginInstanceId}`,
336
- model,
337
- summaryModel,
338
- modelMiddleware,
339
- checkpointer: this.getCheckpointer(),
340
- messages: [
341
- new SystemMessage(systemPrompt),
342
- ...(currentPagePrompt ? [new SystemMessage(currentPagePrompt)] : []),
343
- new HumanMessage(prompt),
344
- ],
345
- adminUser,
346
- adminforth: this.adminforth,
347
- apiBasedTools,
348
- customComponentsDir: this.adminforth.config.customization.customComponentsDir,
414
+ const agentResponse = yield this.runAgentTurn({
415
+ prompt,
349
416
  sessionId,
350
417
  turnId,
418
+ modeName: body.mode,
419
+ userTimeZone,
420
+ currentPage,
421
+ adminUser,
351
422
  httpExtra: {
352
423
  body,
353
424
  query,
@@ -356,70 +427,37 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
356
427
  requestUrl,
357
428
  response,
358
429
  },
359
- userTimeZone,
430
+ sequenceDebugCollector,
360
431
  emitToolCallEvent,
361
- sequenceDebugSink: sequenceDebugCollector,
432
+ emitReasoningDelta: (reasoningDelta) => {
433
+ const reasoningId = startBlock('reasoning');
434
+ send({
435
+ type: 'reasoning-delta',
436
+ id: reasoningId,
437
+ delta: reasoningDelta,
438
+ });
439
+ },
440
+ emitTextDelta: (textDelta) => {
441
+ const textId = startBlock('text');
442
+ fullResponse += textDelta;
443
+ send({
444
+ type: 'text-delta',
445
+ id: textId,
446
+ delta: textDelta,
447
+ });
448
+ },
362
449
  });
363
- try {
364
- for (var _h = true, _j = __asyncValues(stream), _k; _k = yield _j.next(), _b = _k.done, !_b; _h = true) {
365
- _d = _k.value;
366
- _h = false;
367
- const rawChunk = _d;
368
- const [token, metadata] = rawChunk;
369
- const nodeName = typeof (metadata === null || metadata === void 0 ? void 0 : metadata.langgraph_node) === "string"
370
- ? metadata.langgraph_node
371
- : "";
372
- if (nodeName && !["model", "model_request"].includes(nodeName)) {
373
- continue;
374
- }
375
- const blocks = Array.isArray(token === null || token === void 0 ? void 0 : token.contentBlocks)
376
- ? token.contentBlocks
377
- : Array.isArray(token === null || token === void 0 ? void 0 : token.content)
378
- ? token.content
379
- : [];
380
- const reasoningDelta = blocks
381
- .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "reasoning")
382
- .map((b) => { var _a; return String((_a = b.reasoning) !== null && _a !== void 0 ? _a : ""); })
383
- .join("");
384
- const textDelta = blocks
385
- .filter((b) => (b === null || b === void 0 ? void 0 : b.type) === "text")
386
- .map((b) => { var _a; return String((_a = b.text) !== null && _a !== void 0 ? _a : ""); })
387
- .join("");
388
- if (reasoningDelta) {
389
- const reasoningId = startBlock('reasoning');
390
- send({
391
- type: 'reasoning-delta',
392
- id: reasoningId,
393
- delta: reasoningDelta,
394
- });
395
- }
396
- if (textDelta) {
397
- const textId = startBlock('text');
398
- fullResponse += textDelta;
399
- send({
400
- type: 'text-delta',
401
- id: textId,
402
- delta: textDelta,
403
- });
404
- }
405
- }
406
- }
407
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
408
- finally {
409
- try {
410
- if (!_h && !_b && (_c = _j.return)) yield _c.call(_j);
411
- }
412
- finally { if (e_1) throw e_1.error; }
413
- }
450
+ fullResponse = agentResponse.text;
414
451
  }
415
452
  catch (error) {
416
453
  logger.error(`Agent response streaming failed:\n${formatAgentError(error)}`);
417
454
  sequenceDebugCollector.flush();
455
+ fullResponse = formatAgentResponseError(error);
418
456
  const textId = startBlock('text');
419
457
  send({
420
458
  type: 'text-delta',
421
459
  id: textId,
422
- delta: 'Agent response failed. Check server logs for details.',
460
+ delta: fullResponse,
423
461
  });
424
462
  }
425
463
  sequenceDebugCollector.flush();
@@ -434,6 +472,115 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
434
472
  return null;
435
473
  })
436
474
  });
475
+ server.endpoint({
476
+ method: 'POST',
477
+ path: `/agent/speech-response`,
478
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, query, headers, cookies, adminUser, response, requestUrl }) {
479
+ var _b;
480
+ const audioAdapter = this.options.audioAdapter;
481
+ if (!audioAdapter) {
482
+ response.setStatus(400, undefined);
483
+ return {
484
+ error: "Audio adapter is not configured for AdminForth Agent",
485
+ };
486
+ }
487
+ const speechBody = body;
488
+ let transcription;
489
+ try {
490
+ transcription = yield audioAdapter.transcribe({
491
+ buffer: Buffer.from(speechBody.audioBase64, "base64"),
492
+ filename: speechBody.filename,
493
+ mimeType: speechBody.mimeType,
494
+ language: "auto",
495
+ prompt: speechBody.prompt,
496
+ });
497
+ }
498
+ catch (error) {
499
+ logger.error(`Agent speech transcription failed:\n${formatAgentError(error)}`);
500
+ response.setStatus(500, undefined);
501
+ return {
502
+ error: "Speech transcription failed. Check server logs for details.",
503
+ };
504
+ }
505
+ const prompt = transcription.text;
506
+ if (!prompt) {
507
+ response.setStatus(400, undefined);
508
+ return {
509
+ error: "Speech transcription is empty",
510
+ };
511
+ }
512
+ const sessionId = speechBody.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
513
+ const turnId = yield this.createNewTurn(sessionId, prompt);
514
+ yield this.updateSessionDate(sessionId);
515
+ const sequenceDebugCollector = createSequenceDebugCollector();
516
+ let fullResponse = "";
517
+ try {
518
+ const agentResponse = yield this.runAgentTurn({
519
+ prompt,
520
+ sessionId,
521
+ turnId,
522
+ modeName: speechBody.mode,
523
+ userTimeZone: (_b = speechBody.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
524
+ currentPage: speechBody.currentPage,
525
+ adminUser,
526
+ httpExtra: {
527
+ body,
528
+ query,
529
+ headers,
530
+ cookies,
531
+ requestUrl,
532
+ response,
533
+ },
534
+ sequenceDebugCollector,
535
+ emitTextDelta: (textDelta) => {
536
+ fullResponse += textDelta;
537
+ },
538
+ });
539
+ fullResponse = agentResponse.text;
540
+ const speech = yield audioAdapter.synthesize(Object.assign({ text: fullResponse }, speechBody.tts));
541
+ sequenceDebugCollector.flush();
542
+ const turnUpdates = {
543
+ [this.options.turnResource.responseField]: fullResponse,
544
+ };
545
+ if (this.options.turnResource.debugField) {
546
+ turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
547
+ }
548
+ yield this.updateTurn(turnId, turnUpdates);
549
+ return {
550
+ transcript: {
551
+ text: transcription.text,
552
+ language: transcription.language,
553
+ },
554
+ response: {
555
+ text: fullResponse,
556
+ },
557
+ audio: {
558
+ base64: speech.audio.toString("base64"),
559
+ mimeType: speech.mimeType,
560
+ format: speech.format,
561
+ },
562
+ sessionId,
563
+ turnId,
564
+ };
565
+ }
566
+ catch (error) {
567
+ logger.error(`Agent speech response failed:\n${formatAgentError(error)}`);
568
+ sequenceDebugCollector.flush();
569
+ fullResponse = formatAgentResponseError(error);
570
+ const turnUpdates = {
571
+ [this.options.turnResource.responseField]: fullResponse,
572
+ };
573
+ if (this.options.turnResource.debugField) {
574
+ turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
575
+ }
576
+ yield this.updateTurn(turnId, turnUpdates);
577
+ response.setStatus(500, undefined);
578
+ return {
579
+ error: fullResponse,
580
+ };
581
+ }
582
+ })
583
+ });
437
584
  server.endpoint({
438
585
  method: 'POST',
439
586
  path: `/agent/get-sessions`,
@@ -543,6 +690,18 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
543
690
  ok: true
544
691
  };
545
692
  })
546
- });
693
+ }),
694
+ server.endpoint({
695
+ method: 'POST',
696
+ path: `/agent/add-system-message-to-turns`,
697
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, _raw_express_req }) {
698
+ const sessionId = body.sessionId;
699
+ const systemMessage = body.systemMessage;
700
+ yield this.createNewTurn(sessionId, systemMessage);
701
+ return {
702
+ ok: true
703
+ };
704
+ })
705
+ });
547
706
  }
548
707
  }